Changeset 333
- Timestamp:
- 07/16/12 21:21:43 (1 year ago)
- Files:
-
- trunk/tools/tailnmail/tail_n_mail (modified) (59 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/tools/tailnmail/tail_n_mail
r299 r333 6 6 ## Greg Sabino Mullane <greg@endpoint.com> 7 7 ## Selena Deckelmann <selena@chesnok.com> 8 ## See more contributors in the 'Changes' file 8 9 ## BSD licensed 9 10 ## For full documentation, please see: http://bucardo.org/wiki/Tail_n_mail … … 25 26 use 5.008003; 26 27 27 our $VERSION = '1.2 2.1';28 our $VERSION = '1.26.0'; 28 29 29 30 ## Default message subject if not set elsewhere. Keywords replaced: FILE HOST NUMBER UNIQUE … … 81 82 tempfile => -1, 82 83 ## Allow override of the log file to check 83 file => '', 84 file => [], 85 ## Strip out SQLSTATE codes from the FATAL and ERROR messages 86 sqlstate => 0, 84 87 ## Do not send email 85 88 nomail => 0, … … 98 101 ## Send an email even if 0 matches were found 99 102 mailzero => 0, 103 ## Append a signature to the end of mailed messages 104 mailsig => [], 100 105 ## Perform some final prettification of queries 101 106 pretty_query => 1, … … 136 141 'duration=i', 137 142 'tempfile=i', 138 'file=s', 143 'file=s@', 144 'sqlstate', 139 145 'type=s', 140 146 'flatten!', … … 154 160 'mailport=s', 155 161 'mailzero', 162 'mailsig=s@', 156 163 'smtp', 157 164 'tsep=s', … … 200 207 201 208 ## Quick expansion of leading tildes for the file argument 202 $arg{file} =~ s{^~/?}{$ENV{HOME}/}; 209 for (@{ $arg{file} }) { 210 s{^~/?}{$ENV{HOME}/}; 211 } 203 212 204 213 ## Save away our hostname 205 214 my $hostname = qx{hostname}; 206 215 chomp $hostname; 207 208 ## We use a CURRENT key for future expansion209 my $curr = 'CURRENT';210 216 211 217 ## Global option variables … … 234 240 ## Process the log_line_prefix and change it into a regex 235 241 my ($havetime,$havepid) = (0,0); 236 $llp =~ s/([\-\[\]])/\\$1/g; 242 243 ## Escape certain things that may confuse the regex parser 244 $llp =~ s/([\-\[\]\(\)])/\\$1/g; 245 237 246 ## This assumes timestamp comes before the pid! 238 247 $llp =~ s/%t/(\\d\\d\\d\\d\-\\d\\d\-\\d\\d \\d\\d:\\d\\d:\\d\\d \\w\\w\\w\\w?)/ and $havetime=1; … … 251 260 $llp =~ s/%r/\\S*/; 252 261 $llp =~ s/%h/\\S*/; 262 $llp =~ s/%a/\\S*/; 253 263 254 264 if ($arg{pglog} eq 'syslog') { … … 300 310 301 311 ## Global regex: may change per file 302 my ($exclude, $include );312 my ($exclude, $include, $exclude_prefix); 303 313 304 314 ## Note if we bumped into maxsize when trying to read a file … … 331 341 ## If they requested no lastfile, remove it now 332 342 if ($arg{nolastfile}) { 333 delete $opt{ $curr}{lastfile};343 delete $opt{lastfile}; 334 344 } 335 345 346 ## If they want a mail signature, open the file(s) and read it in now 347 if (defined $arg{mailsig}->[0]) { 348 ## Combine all files in order into a single string 349 my $sigstring = ''; 350 my $fh; 351 for my $sigfile (@{$arg{mailsig}}) { 352 $sigfile =~ s{^~/?}{$ENV{HOME}/}; 353 if (! open $fh, '<', $sigfile) { 354 warn qq{Could not open signature file "$sigfile": $!\n}; 355 exit 1; 356 } 357 { local $/; $sigstring .= <$fh>; } 358 close $fh or warn qq{Could not close "$sigfile": $!\n}; 359 } 360 $arg{mailsignature} = $sigstring; 361 } 336 362 337 363 ## Parse each file returned by pick_log_file until we start looping 338 364 my $last_logfile = ''; 339 365 my @files_parsed; 366 my $filenumber = 0; 367 my $fileinfo = $opt{file}[$filenumber]; 368 340 369 { 370 341 371 ## Generate the next log file to parse 342 my $logfile = pick_log_file(); 343 344 ## If undefined, simply exit the loop 345 last if ! defined $logfile; 346 347 ## If it's the same as the last one we did, we are done 348 last if $last_logfile eq $logfile; 372 my $logfile = pick_log_file($fileinfo); 373 374 ## If undefined or same as last time, we are done with this file 375 if (! defined $logfile or $logfile eq $last_logfile) { 376 ## Grab the next extry 377 $fileinfo = $opt{file}[++$filenumber]; 378 ## No more file? We are done! 379 last if ! defined $fileinfo; 380 # Otherwise, loop back with the new fileinfo 381 redo; 382 } 349 383 350 384 $arg{debug} and warn " Parsing file: $logfile\n"; 351 385 352 ## Force to CSV mode if the file ends in csv 353 $logfile =~ /\.csv/ and $arg{pglog} = 'csv'; 354 355 my $count = parse_file($logfile); 386 my $count = parse_file($logfile, $fileinfo); 356 387 357 388 if ($count >= 0) { … … 365 396 } 366 397 367 $opt{$curr}{filename} = $last_logfile;368 369 398 ## We're done parsing the message, send an email if needed 370 process_report() if $opt{grand_total} or $arg{mailzero} or $opt{ $curr}{mailzero};399 process_report() if $opt{grand_total} or $arg{mailzero} or $opt{mailzero}; 371 400 final_cleanup(); 372 401 … … 377 406 378 407 ## Figure out which files we need to parse 408 ## Sole argument is a hashref of file information: 409 ## name: logfile to open 410 ## original: original name 411 ## lastfile: we scanned last time this ran. May be an empty string or not exist 412 413 my $info = shift; 414 415 my $name = $info->{name} or die 'No name for the file found!'; 416 my $orig = $info->{original} or die 'No original file found!'; 417 my $lastfile = $info->{lastfile} || ''; 379 418 380 419 ## Basic flow: … … 382 421 ## Then walk forward until we hit the most recent one 383 422 384 ## If a custom file, we always just return the main filename385 ## We also remove the lastfile, as it's not important anymore386 ## Same for a reset - we only want the latest file387 if ($arg{file} or $arg{reset}) {388 delete $opt{$curr}{lastfile};389 return $arg{file} || $opt{$curr}{filename};390 }391 392 my $lastfile = $opt{$curr}{lastfile};393 my $orig = $opt{$curr}{original_filename} || '';394 395 423 ## Handle the LATEST case right away 396 424 if ($orig =~ s{([^/\\]*)LATEST([^/\\]*)$}{}o) { … … 401 429 ## We need all files newer than that one, in order, until we run out 402 430 403 ## Already have the list? Pop off items until we are done 404 if (exists $opt{$curr}{middle_filenames}) { 405 ## Return the next file, or undef when we run out 406 return pop @{$opt{$curr}{middle_filenames}}; 407 } 408 409 my $dir = $orig; 410 $dir =~ s{/\z}{}; 411 -d $dir or die qq{Cannot open $dir: not a directory!\n}; 412 opendir my $dh, $dir or die qq{Could not opendir "$dir": $!\n}; 413 414 ## We need the modification time of the lastfile 415 my $lastfiletime = defined $lastfile ? -M $lastfile : 0; 416 417 my %fileq; 418 while (my $file = readdir($dh)) { 419 my $fname = "$dir/$file"; 420 my $modtime = -M $fname; 421 ## Skip if not a normal file 422 next if ! -f _; 423 if (length $prefix or length $postfix) { 424 next if $file !~ /\A\Q$prefix\E.*\Q$postfix\E\z/o; 425 } 426 ## Skip if it's older than the lastfile 427 next if $lastfiletime and $modtime > $lastfiletime; 428 $fileq{$modtime}{$fname} = 1; 429 } 430 closedir $dh or warn qq{Could not closedir "$dir": $!\n}; 431 TF: for my $time (sort { $a <=> $b } keys %fileq) { 432 for my $file (sort keys %{$fileq{$time}}) { 433 push @{$opt{$curr}{middle_filenames}} => $file; 434 ## If we don't have a lastfile, we simply use the most recent file 435 ## and throw away the rest 436 last TF if ! $lastfiletime; 437 } 438 } 439 440 return pop @{$opt{$curr}{middle_filenames}}; 431 ## If we don't have the list already, build it now 432 if (! exists $opt{middle_filenames}) { 433 434 my $dir = $orig; 435 $dir =~ s{/\z}{}; 436 -d $dir or die qq{Cannot open $dir: not a directory!\n}; 437 opendir my $dh, $dir or die qq{Could not opendir "$dir": $!\n}; 438 439 ## We need the modification time of the lastfile 440 my $lastfiletime = defined $lastfile ? -M $lastfile : 0; 441 442 my %fileq; 443 while (my $file = readdir($dh)) { 444 my $fname = "$dir/$file"; 445 my $modtime = -M $fname; 446 ## Skip if not a normal file 447 next if ! -f _; 448 if (length $prefix or length $postfix) { 449 next if $file !~ /\A\Q$prefix\E.*\Q$postfix\E\z/o; 450 } 451 ## Skip if it's older than the lastfile 452 next if $lastfiletime and $modtime > $lastfiletime; 453 $fileq{$modtime}{$fname} = 1; 454 } 455 closedir $dh or warn qq{Could not closedir "$dir": $!\n}; 456 457 TF: for my $time (sort { $a <=> $b } keys %fileq) { 458 for my $file (sort keys %{$fileq{$time}}) { 459 push @{$opt{middle_filenames}} => $file; 460 ## If we don't have a lastfile, we simply use the most recent file 461 ## and throw away the rest 462 last TF if ! $lastfiletime; 463 } 464 } 465 } 466 467 ## Return the next file, or undef when we run out 468 my $nextfile = pop @{ $opt{middle_filenames} }; 469 ## If we are done, remove this temp hash 470 if (! defined $nextfile) { 471 delete $opt{middle_filenames}; 472 } 473 return $nextfile; 441 474 442 475 } ## end of LATEST time travel 443 476 444 477 ## No lastfile makes it easy 445 exists $opt{$curr}{lastfile} or return $opt{$curr}{filename};478 return $name if ! $lastfile; 446 479 447 480 ## If we haven't processed the lastfile, do that one first 448 exists $find{$lastfile} or return $lastfile;449 450 ## If the last is the same as the current, return 451 $lastfile eq $opt{$curr}{filename} and return $lastfile;481 return $lastfile if ! exists $find{$lastfile}; 482 483 ## If the last is the same as the current, return the name 484 return $name if $lastfile eq $name; 452 485 453 486 ## We've processed the last file, are there any files in between the two? … … 455 488 if ($orig =~ /%/) { 456 489 457 ## Already have the list? Pop off items until we are done 458 if (exists $opt{$curr}{middle_filenames}) { 459 my $newfile = pop @{$opt{$curr}{middle_filenames}}; 460 ## When we run out, return the current file 461 return $newfile || $opt{$curr}{filename}; 462 } 463 464 ## We're going to walk backwards, 30 minutes at a time, and gather up 465 ## all files between "now" and the "last" 466 my $timerewind = 60*30; ## 30 minutes 467 my $maxloops = 24*2 * 7 * 30; ## max of 30 days 468 my $bail = 0; 469 my %seenfile; 470 my $lastchecked = ''; 471 BACKINTIME: { 472 473 my @ltime = localtime(time - $timerewind); 474 my $newfile = strftime($orig, @ltime); 475 if ($newfile ne $lastchecked) { 476 last if $newfile eq $lastfile; 477 $arg{debug} and warn "Checking for file $newfile (last was $lastfile)\n"; 478 if (! exists $seenfile{$newfile}) { 479 $seenfile{$newfile} = 1; 480 push @{$opt{$curr}{middle_filenames}} => $newfile; 490 ## Build the list if we don't have it yet 491 if (! exists $opt{middle_filenames}) { 492 493 ## We're going to walk backwards, 30 minutes at a time, and gather up 494 ## all files between "now" and the "last" 495 my $timerewind = 60*30; ## 30 minutes 496 my $maxloops = 24*2 * 7 * 30; ## max of 30 days 497 my $bail = 0; 498 my %seenfile; 499 my $lastchecked = ''; 500 BACKINTIME: { 501 502 my @ltime = localtime(time - $timerewind); 503 my $newfile = strftime($orig, @ltime); 504 if ($newfile ne $lastchecked) { 505 last if $newfile eq $lastfile; 506 $arg{debug} and warn "Checking for file $newfile (last was $lastfile)\n"; 507 if (! exists $seenfile{$newfile}) { 508 $seenfile{$newfile} = 1; 509 push @{$opt{middle_filenames}} => $newfile; 510 } 511 $lastchecked = $newfile; 481 512 } 482 $lastchecked = $newfile; 483 } 484 485 $timerewind += 60*30; 486 ++$bail > $maxloops and die "Too many loops ($bail): bailing\n"; 487 redo; 488 } 489 490 return (keys %seenfile) ? (pop @{$opt{$curr}{middle_filenames}}) : $opt{$curr}{filename}; 513 514 $timerewind += 60*30; 515 ++$bail > $maxloops and die "Too many loops ($bail): bailing\n"; 516 redo; 517 } 518 519 } 520 521 ## If the above loop found nothing, return the current name 522 if (! exists $opt{middle_filenames}) { 523 return $name; 524 } 525 526 ## Otherwise, pull it off the list until there is nothing left 527 my $nextfile = pop @{ $opt{middle_filenames} }; 528 ## If we are done, remove this temp hash 529 if (! defined $nextfile) { 530 delete $opt{middle_filenames}; 531 } 532 return $nextfile; 491 533 } 492 534 493 535 ## Just return the current file 494 return $ opt{$curr}{filename};536 return $name; 495 537 496 538 } ## end of pick_log_file … … 521 563 while (<$rc>) { 522 564 next if /^\s*#/; 523 next if ! /^\s*( \w+)\s*[=:]\s*(.+?)\s*$/o;565 next if ! /^\s*([\w\_\-]+)\s*[=:]\s*(.+?)\s*$/o; 524 566 my ($name,$value) = (lc $1,$2); 525 $opt{$ curr}{$name} = $value;567 $opt{$name} = $value; 526 568 $arg{$name} = $value; 527 569 ## If we are disabled, simply exit quietly … … 582 624 583 625 ## A non-comment after one or comments allows us to map them to each other 584 if (@comment and m{^( \w+):}) {626 if (@comment and m{^([\w\_\-]+):}) { 585 627 chomp; 586 628 for my $c (@comment) { … … 593 635 } 594 636 595 ## What file are we checking on? 596 if (/^FILE:\s*(.+?)\s*$/) { 597 my $filename = $localopt{original_filename} = $1; 598 637 ## What file(s) are we checking on? 638 if (/^FILE(\d*):\s*(.+?)\s*$/) { 639 640 my $suffix = $1 || 0; 641 my $filename = $2; 642 643 ## Basic sanity check 599 644 if ($filename !~ /\w/) { 600 die "No FILE found in the config file! (tried: $filename)\n"; 601 } 602 603 $filename = transform_filename($filename); 604 605 ## If a custom file was specified, use that instead 606 if ($arg{file}) { 607 ## If it contains a path, use it directly 608 if ($arg{file} =~ m{/}) { 609 $filename = $arg{file}; 645 die "No valid FILE found in the config file! (tried: $filename)\n"; 646 } 647 648 ## If files were specified on the command line, use those instead 649 if ($arg{file}[0]) { 650 651 ## Have we been here already? Only need to override once 652 if (! exists $localopt{filename}) { 653 654 for my $argfile (@{ $arg{file} }) { 655 656 ## If it contains a path, use it directly 657 if ($argfile =~ m{/}) { 658 $filename = $argfile; 659 } 660 ## Otherwise, replace the current file name but keep the directory 661 else { 662 my $dir = dirname($filename); 663 $filename = "$dir/$argfile"; 664 } 665 666 ## Add it to our local list both as final and original name 667 push @{ $localopt{file} } => { 668 name => $filename, 669 original => $filename, 670 commandline => 1, 671 lastfile => '', 672 offset => 0, 673 }; 674 } 675 676 next; 610 677 } 611 ## Otherwise, replace the current file name but keep the directory 612 else { 613 my $dir = dirname($filename); 614 $filename = "$dir/$arg{file}"; 615 } 616 } 617 618 ## Set some default values 619 $localopt{filename} = $filename; 620 $localopt{exclude} ||= []; 621 $localopt{include} ||= []; 622 $localopt{email} ||= []; 623 678 } 679 680 ## If the file contains % escapes, replace with the actual time 681 my $newfilename = transform_filename($filename); 682 683 ## Save to the local list, storing the original filename for config rewriting 684 push @{ $localopt{file} } => 685 { 686 name => $newfilename, 687 original => $filename, 688 suffix => $suffix, 689 }; 624 690 } ## end of FILE: 625 691 626 692 ## The last filename we used 627 elsif (/^LASTFILE:\s*(.+?)\s*$/) { 628 $localopt{lastfile} = $1; 693 elsif (/^LASTFILE(\d*):\s*(.+?)\s*$/) { 694 my $suffix = $1 || 1; 695 $localopt{lastfile}{$suffix} = $2; 629 696 } 630 697 ## Who to send emails to for this file … … 668 735 } 669 736 } 737 ## Allow a very local log_line_prefix 738 elsif (/^LOG_LINE_PREFIX:\s*(.+)/) { 739 $arg{log_line_prefix} = $localopt{log_line_prefix} = $1; 740 } 670 741 ## How to sort the output 671 742 elsif (/^SORTBY:\s*(\w+)/) { … … 684 755 push @{$localopt{exclude}}, $1; 685 756 } 757 ## Which prefix lines to exclude from the report 758 elsif (/^EXCLUDE_PREFIX:\s*(.+?)\s*$/) { 759 push @{$localopt{exclude_prefix}}, $1; 760 } 686 761 ## Which lines to include in the report 687 762 elsif (/^INCLUDE:\s*(.+)/) { 688 763 push @{$localopt{include}}, $1; 689 764 } 690 ## The current offset into the file 691 elsif (/^OFFSET:\s*(\d+)/) { 692 $localopt{offset} = $1; 693 } 694 ## The custom maxsize for this file 765 ## The current offset into a file 766 elsif (/^OFFSET(\d*):\s*(\d+)/) { 767 my $suffix = $1 || 1; 768 $localopt{offset}{$suffix} = $2; 769 } 770 ## The custom maxsize for all files 695 771 elsif (/^MAXSIZE:\s*(\d+)/) { 696 772 $localopt{maxsize} = $1; 697 773 } 698 ## The subject line for this file774 ## The subject line 699 775 elsif (/^MAILSUBJECT:\s*(.+)/) { ## Trailing whitespace is significant here 700 776 $localopt{mailsubject} = $1; … … 705 781 $localopt{mailzero} = $1; 706 782 } 783 ## Allow (possibly multiple) mail signatures 784 elsif (/^MAILSIG:\s*(.+)/) { 785 push @{$localopt{mailsig}}, $1; 786 push @{$arg{mailsig}}, $1; 787 } 707 788 ## Size at which we cutoff long statements 708 789 elsif (/^STATEMENT_SIZE:\s*(.+)/) { … … 711 792 } 712 793 close $c or die qq{Could not close "$configfile": $!\n}; 794 795 ## Adjust the file suffixes as needed 796 ## This allows us to simply add multiple bare 'FILE:' entries before the first rewrite 797 ## We also plug in the LASTFILE AND OFFSET values now 798 my %numused; 799 for my $file (@{ $localopt{file} }) { 800 $file->{suffix} ||= 0; 801 next if ! $file->{suffix}; 802 if ($numused{$file->{suffix}}++) { 803 die "The same FILE suffix ($file->{suffix}) was used more than once!\n"; 804 } 805 } 806 for my $file (@{ $localopt{file} }) { 807 808 ## No need to change anything if we forced via the command line 809 next if $file->{commandline}; 810 811 ## Only need to adjust 0s 812 if (! $file->{suffix}) { 813 814 ## Replace with the first free number 815 my $x = 1; 816 { 817 if (! $numused{$x}++) { 818 $file->{suffix} = $x; 819 last; 820 } 821 if ($x++ > 999) { 822 die "Something went wrong: 999 iterations to find a FILE suffix!\n"; 823 } 824 redo; 825 } 826 } 827 828 ## Put the lastfile into place if it exists 829 $file->{lastfile} = $localopt{lastfile}{$file->{suffix}} || ''; 830 831 ## Put the offset into place if it exists 832 $file->{offset} = $localopt{offset}{$file->{suffix}} || 0; 833 834 } 713 835 714 836 ## Move the local vars into place, also record that we found them here … … 722 844 } 723 845 } 724 $opt{$curr}{$k} = $localopt{$k}; 725 } 846 $opt{$k} = $localopt{$k}; 847 } 848 726 849 if ($arg{debug}) { 727 850 local $Data::Dumper::Varname = 'opt'; 851 warn Dumper \%opt; 852 local $Data::Dumper::Varname = 'arg'; 728 853 warn Dumper \%arg; 729 854 } … … 738 863 ## Call parse_inherit_file on each item in $opt{inherit} 739 864 740 for my $file (@{$opt{ $curr}{inherit}}) {865 for my $file (@{$opt{inherit}}) { 741 866 parse_inherit_file($file); 742 867 } … … 819 944 ## How to sort the output 820 945 elsif (/^SORTBY:\s*(\w+)/) { 821 $opt{ $curr}{sortby} = $1;946 $opt{sortby} = $1; 822 947 } 823 948 ## Which lines to exclude from the report 824 949 elsif (/^EXCLUDE:\s*(.+?)\s*$/) { 825 push @{$opt{$curr}{exclude}}, $1; 950 push @{$opt{exclude}}, $1; 951 } 952 ## Which prefix lines to exclude from the report 953 elsif (/^EXCLUDE_PREFIX:\s*(.+?)\s*$/) { 954 push @{$opt{exclude_prefix}}, $1; 826 955 } 827 956 ## Which lines to include in the report 828 957 elsif (/^INCLUDE:\s*(.+)/) { 829 push @{$opt{ $curr}{include}}, $1;958 push @{$opt{include}}, $1; 830 959 } 831 960 ## Maximum file size 832 961 elsif (/^MAXSIZE:\s*(\d+)/) { 833 $opt{ $curr}{maxsize} = $1;962 $opt{maxsize} = $1; 834 963 } 835 964 ## Exclude durations below this number … … 859 988 ## Who to send emails from 860 989 elsif (/^FROM:\s*(.+?)\s*$/) { 861 $opt{ $curr}{from} = $1;990 $opt{from} = $1; 862 991 } 863 992 ## Who to send emails to for this file 864 993 elsif (/^EMAIL:\s*(.+?)\s*$/) { 865 push @{$opt{ $curr}{email}}, $1;994 push @{$opt{email}}, $1; 866 995 } 867 996 ## Force mail to be sent - overrides any other setting 868 997 elsif (/^MAILZERO:\s*(.+)/) { 869 $opt{ $curr}{mailzero} = $1;998 $opt{mailzero} = $1; 870 999 } 871 1000 ## The file to use 872 elsif (/^FILE:\s*(.+)/) { 873 $opt{$curr}{original_filename} = $1; 874 $opt{$curr}{filename} = transform_filename($1); 875 $opt{$curr}{inherited_filename} = 1; 1001 elsif (/^FILE(\d*):\s*(.+)/) { 1002 1003 my $suffix = $1 || 0; 1004 my $filename = $2; 1005 1006 ## Skip entirely if we have a command-line file request 1007 ## This is handled in the main config parsing 1008 next if $arg{file}[0]; 1009 1010 ## As with the normal config file, store a temp version 1011 ## Save to the local list, storing the original filename for config rewriting 1012 push @{ $opt{tempifile} } => 1013 { 1014 original => $filename, 1015 suffix => $suffix, 1016 }; 876 1017 } 877 1018 ## The mail subject 878 1019 elsif (/^MAILSUBJECT:\s*(.+)/) { 879 $opt{$curr}{mailsubject} = $1; 880 $opt{$curr}{customsubject} = 1; 1020 $opt{mailsubject} = $1; 1021 $opt{customsubject} = 1; 1022 } 1023 ## The mail signature 1024 elsif (/^MAILSIG:\s*(.+)/) { 1025 push @{$opt{mailsig}}, $1; 881 1026 } 882 1027 ## The log line prefix … … 886 1031 ## Size at which we cutoff long statements 887 1032 elsif (/^STATEMENT_SIZE:\s*(.+)/) { 888 $opt{ $curr}{statement_size} = $1;1033 $opt{statement_size} = $1; 889 1034 } 890 1035 else { … … 895 1040 close $fh or warn qq{Could not close file "$file": $!\n}; 896 1041 1042 ## Merge all the "FILE" entries and adjust suffixes 1043 ## We allow overlap between the normal and inherited lists 1044 1045 if (exists $opt{tempifile}) { 1046 1047 my %numused; 1048 for my $file (@{ $opt{tempifile} }) { 1049 $file->{suffix} ||= 0; 1050 next if ! $file->{suffix}; 1051 if ($numused{$file->{suffix}}++) { 1052 die "The same FILE suffix ($file->{suffix}) was used more than once inside $filename!\n"; 1053 } 1054 } 1055 1056 for my $file (@{ $opt{tempifile} }) { 1057 1058 ## Change zero to the first free number 1059 if (! $file->{suffix}) { 1060 my $x = 1; 1061 { 1062 if (! $numused{$x}++) { 1063 $file->{suffix} = $x; 1064 last; 1065 } 1066 if ($x++ > 999) { 1067 die "Something went wrong: 999 iterations to find a FILE suffix inside $filename!\n"; 1068 } 1069 redo; 1070 } 1071 } 1072 1073 ## Create our real entry 1074 push @{ $opt{file} } => 1075 { 1076 name => transform_filename($file->{original}), 1077 original => $file->{original}, 1078 suffix => $file->{suffix}, 1079 inherited => 1, 1080 }; 1081 } 1082 1083 ## Remove our temporary list 1084 delete $opt{tempifile}; 1085 } 1086 897 1087 return; 898 1088 … … 902 1092 sub parse_file { 903 1093 904 ## Parse the passed in file 1094 ## Parse a file - this is the workhorse 1095 ## Arguments: two 1096 ## 1. Exact filename we are parsing 1097 ## 2. Hashref of file information: 1098 ## name: logfile to open 1099 ## original: original name 1100 ## lastfile: we scanned last time this ran. May be an empty string or not exist 1101 ## offset: where in the file we stopped at last time 905 1102 ## Returns the number of matches 906 1103 907 1104 my $filename = shift; 1105 my $fileinfo = shift; 1106 1107 ## The file we scanned last time we ran 1108 my $lastfile = $fileinfo->{lastfile} || ''; 1109 1110 ## Set this as the latest (but not the lastfile) 1111 $fileinfo->{latest} = $filename; 908 1112 909 1113 ## Touch the hash so we know we've been here … … 923 1127 my $size = -s $filename; 924 1128 my $offset = 0; 925 my $maxsize = $opt{ $curr}{maxsize} ? $opt{$curr}{maxsize} : $arg{maxsize};1129 my $maxsize = $opt{maxsize} ? $opt{maxsize} : $arg{maxsize}; 926 1130 927 1131 ## Is the offset significant? 928 ## Usually only is if the stored offset matches the current file 929 930 if (!$arg{file} 931 and (!exists $opt{$curr}{lastfile} or ($opt{$curr}{lastfile} eq $filename)) 1132 if (!$arg{file}[0] ## ...not if we passed in filenames manually 1133 and $lastfile eq $filename ## ...not if this is not the same file we got the offset for last time 932 1134 ) { 933 1135 ## Allow the offset to equal the size via --reset 934 1136 if ($arg{reset}) { 935 $offset = $ opt{$curr}{newoffset} = $size;1137 $offset = $size; 936 1138 $arg{verbose} and warn " Resetting offset to $offset\n"; 937 1139 } … … 946 1148 } 947 1149 } 948 els if (exists $opt{$curr}{offset}){949 $offset = $ opt{$curr}{offset};1150 else{ 1151 $offset = $fileinfo->{offset} || 0; 950 1152 } 951 1153 } … … 963 1165 964 1166 ## If the offset is equal to the size, we're done! 965 return 0 if $offset >= $size; 1167 ## Store the offset if it is truly new and significant 1168 if ($offset >= $size) { 1169 $offset = $size; 1170 if ($offset and $fileinfo->{offset} != $offset) { 1171 $opt{newoffset}{$filename} = $offset; 1172 } 1173 return 0; 1174 } 966 1175 967 1176 ## Store the original offset … … 1021 1230 1022 1231 } ## end pos > 1 1232 1023 1233 } ## end find_line_number 1024 1234 1025 1235 ## Get exclusion and inclusion regexes for this file 1026 ($exclude,$include ) = generate_regexes($filename);1236 ($exclude,$include,$exclude_prefix) = generate_regexes($filename); 1027 1237 1028 1238 ## Discard the previous line if needed (we rewound by 10 characters above) … … 1035 1245 my %pidline; 1036 1246 1037 if (lc $arg{pglog} eq 'csv') { 1247 ## Switch to CSV mode if required of if the file ends in '.csv' 1248 if (lc $arg{pglog} eq 'csv' or $filename =~ /\.csv$/) { 1038 1249 if (! defined $csv) { 1039 1250 eval { … … 1069 1280 my $lastline = ''; 1070 1281 my $syslognum = 0; ## used by syslog only 1282 my $bailout = 0; ## emergency bail out in case we end up sleep seeking 1071 1283 LOGLINE: while (<$fh>) { 1284 1285 ## We ran into a truncated line last time, so we are most likely done 1286 last if $bailout; 1287 1072 1288 ## Easiest to just remove the newline here and now 1073 chomp; 1289 if (! chomp) { 1290 ## There was no newline, so it's possible some other process is in 1291 ## the middle of writing this line. Just in case this is so, sleep and 1292 ## let it finish, then try again. Because we don't want to turn this 1293 ## into a tail -f situation, bail out of the loop once done 1294 sleep 1; 1295 ## Rewind just far enough to try this line again 1296 seek $fh, - (length $_), 1; 1297 $_ = <$fh>; 1298 if (! chomp) { 1299 ## Still no go! Let's just leave and abandon this line 1300 last LOGLINE; 1301 } 1302 ## Success! Finish up this line, but then abandon any further slurping 1303 $bailout = 1; 1304 } 1074 1305 if ($arg{pgmode}) { 1075 1306 ## 1=prefix 2=timestamp 3=PID 4=rest 1076 1307 if ($_ =~ s/$pgpidre/$4/) { 1077 1308 1078 $pgprefix = $1; 1309 ## We want the timestamp and the pid, even if we have to fake it 1310 ($pgprefix,$pgts,$pgpid,$pgnum) = ($1, $2||'', $3||1, 1); 1311 1079 1312 $pgprefix =~ s/\s+$//o; 1080 ## We want the timestamp and the pid, even if we have to fake it1081 ($pgts,$pgpid,$pgnum) = ($2||'',$3||1, 1);1082 1313 if ($arg{pglog} eq 'syslog') { 1083 1314 if ($pgprefix =~ /\[(\d+)\-\d+/) { … … 1117 1348 } 1118 1349 1350 ## Optionally strip out SQLSTATE codes 1351 if ($arg{sqlstate}) { 1352 $_ =~ s/^(?:FATAL|ERROR): ([0-9A-Z]{5}): /ERROR: /o; 1353 } 1354 1119 1355 ## Assign this string to the current pgnum slot 1120 1356 $pidline{$pgpid}{string}{$pgnum} = $_; … … 1185 1421 ## Get the new offset and store it 1186 1422 seek $fh, 0, 1; 1187 $opt{$curr}{newoffset} = tell $fh; 1423 $offset = tell $fh; 1424 if ($fileinfo->{offset} != $offset and $offset) { 1425 $opt{newoffset}{$filename} = $offset; 1426 } 1188 1427 1189 1428 close $fh or die qq{Could not close "$filename": $!\n}; … … 1230 1469 ## Currently, all files get the same regex, so we cache it 1231 1470 if (exists $opt{globalexcluderegex}) { 1232 return $opt{globalexcluderegex}, $opt{globalincluderegex}; 1471 return $opt{globalexcluderegex}, $opt{globalincluderegex}, $opt{globalexcludeprefixregex}; 1472 1233 1473 } 1234 1474 1235 1475 ## Build an exclusion regex 1236 1476 my $lexclude = ''; 1237 for my $ex (@{$opt{ $curr}{exclude}}) {1477 for my $ex (@{$opt{exclude}}) { 1238 1478 $arg{debug} and warn " Adding exclusion: $ex\n"; 1239 1479 my $regex = qr{$ex}; … … 1243 1483 $arg{verbose} and $lexclude and warn " Exclusion: $lexclude\n"; 1244 1484 1485 ## Build a prefix exclusion regex 1486 my $lexclude_prefix = ''; 1487 for my $ex (@{$opt{exclude_prefix}}) { 1488 $arg{debug} and warn " Adding exclusion_prefix: $ex\n"; 1489 my $regex = qr{$ex}; 1490 $lexclude_prefix .= "$regex|"; 1491 } 1492 $lexclude_prefix =~ s/\|$//; 1493 $arg{verbose} and $lexclude_prefix and warn " Exclusion_prefix: $lexclude_prefix\n"; 1494 1245 1495 ## Build an inclusion regex 1246 1496 my $linclude = ''; 1247 for my $in (@{$opt{ $curr}{include}}) {1497 for my $in (@{$opt{include}}) { 1248 1498 $arg{debug} and warn " Adding inclusion: $in\n"; 1249 1499 my $regex = qr{$in}; … … 1254 1504 1255 1505 $opt{globalexcluderegex} = $lexclude; 1506 $opt{globalexcludeprefixregex} = $lexclude_prefix; 1256 1507 $opt{globalincluderegex} = $linclude; 1257 1508 1258 return $lexclude, $linclude ;1509 return $lexclude, $linclude, $lexclude_prefix; 1259 1510 1260 1511 } ## end of generate_regexes … … 1313 1564 ## Bail if it matches the exclusion regex 1314 1565 return 0 if $exclude and $string =~ $exclude; 1566 1567 ## Bail if it matches the prefix exclusion regex 1568 return 0 if $exclude_prefix and $pgprefix =~ $exclude_prefix; 1315 1569 } 1316 1570 … … 1318 1572 if ($arg{type} eq 'duration' and $arg{duration} >= 0) { 1319 1573 return 0 if ($string =~ / duration: (\d+)/o and $1 < $arg{duration}); 1320 }1321 1322 ## If in tempfile mode, and we have a minimum cutoff, discard smaller ones1323 if ($arg{type} eq 'tempfile' and $arg{tempfile} >= 0) {1324 #return 0 if ($string =~ / temporary file:.+?size (\d+)/o and $1 < $arg{tempfile});1325 1574 } 1326 1575 … … 1380 1629 }geix; 1381 1630 1382 my $thisletter ;1631 my $thisletter = ''; 1383 1632 1384 1633 $string =~ s{(VALUES|REPLACE)\s*\((.+)\)}{ ## For emacs: ()()() … … 1717 1966 1718 1967 ## Subject with replaced keywords: 1719 my $subject = $opt{ $curr}{mailsubject} || $DEFAULT_SUBJECT;1968 my $subject = $opt{mailsubject} || $DEFAULT_SUBJECT; 1720 1969 $subject =~ s/FILE/$last_file_parsed/g; 1721 1970 $subject =~ s/HOST/$hostname/g; 1722 if ($arg{tsepnosub} or $opt{ $curr}{tsepnosub}) {1971 if ($arg{tsepnosub} or $opt{tsepnosub}) { 1723 1972 $subject =~ s/NUMBER/$grand_total/g; 1724 1973 $subject =~ s/UNIQUE/$unique_matches/g; … … 1740 1989 1741 1990 ## Allow no specific email for dryruns 1742 if (! @{$opt{$curr}{email}}) {1991 if (! exists $opt{email} or ! @{$opt{email}}) { 1743 1992 if ($arg{dryrun} or $arg{nomail}) { 1744 push @{$opt{ $curr}{email}} => 'dryrun@example.com';1993 push @{$opt{email}} => 'dryrun@example.com'; ## no critic (RequireInterpolationOfMetachars) 1745 1994 } 1746 1995 } 1747 1996 1748 1997 ## Fill out the "To:" fields 1749 for my $email (@{$opt{ $curr}{email}}) {1998 for my $email (@{$opt{email}}) { 1750 1999 push @header => "To: $email"; 1751 2000 } 1752 if (! @{$opt{ $curr}{email}}) {2001 if (! @{$opt{email}}) { 1753 2002 die "Cannot send email without knowing who to send to!\n"; 1754 2003 } 1755 2004 1756 my $mailcom = $opt{ $curr}{mailcom} || $arg{mailcom};2005 my $mailcom = $opt{mailcom} || $arg{mailcom}; 1757 2006 1758 2007 ## Custom From: 1759 my $from_addr = $opt{ $curr}{from} || '';2008 my $from_addr = $opt{from} || ''; 1760 2009 if ($from_addr ne '') { 1761 2010 push @header => "From: $from_addr"; … … 1788 2037 push @msg => "Total matches: $pretty_grand_total"; 1789 2038 my $maxcount = 1; 2039 my $maxname = 1; 1790 2040 my $maxletter = 1; 1791 2041 for my $file (@files_parsed) { … … 1793 2043 $file->[1] = pretty_number($file->[1]); 1794 2044 $maxcount = length $file->[1] if length $file->[1] > $maxcount; 2045 $maxname = length $file->[0] if length $file->[0] > $maxname; 1795 2046 my $name = chr(65+$letter); 1796 2047 if ($letter >= 26) { … … 1805 2056 next if ! $file->[1]; 1806 2057 my $name = $fab{$file->[0]}; 1807 push @msg => sprintf 'Matches from %-*s % s:%*s',2058 push @msg => sprintf 'Matches from %-*s %-*s %*s', 1808 2059 $maxletter + 2, 1809 2060 "[$name]", 1810 $file->[0], 2061 $maxname+1, 2062 "$file->[0]:", 1811 2063 $maxcount, 1812 2064 $file->[1]; … … 1845 2097 } 1846 2098 1847 my $emails = join ' ' => @{$opt{ $curr}{email}};2099 my $emails = join ' ' => @{$opt{email}}; 1848 2100 1849 2101 ## Sanity check on number of loops below … … 1938 2190 last if tell $bigfh >= $stop_point; 1939 2191 } 2192 2193 ## If we have a signature, add it 2194 if ($arg{mailsignature}) { 2195 ## Caller's responsibility to add a "--" line 2196 print {$efh} $arg{mailsignature}; 2197 } 2198 1940 2199 close $efh or warn qq{Could not close $emailfile: $!\n}; 1941 2200 … … 1946 2205 } 1947 2206 else { 1948 my $mailmode = $opt{ $curr}{mailmode} || $arg{mailmode};2207 my $mailmode = $opt{mailmode} || $arg{mailmode}; 1949 2208 if ($arg{mailmode} eq 'sendmail') { 1950 2209 system $COM; … … 2008 2267 2009 2268 ## Absorb any values set by rc files, and sanity check things 2010 my $mailserver = $opt{ $curr}{mailserver} || $arg{mailserver};2269 my $mailserver = $opt{mailserver} || $arg{mailserver}; 2011 2270 if ($mailserver eq 'example.com') { 2012 2271 die qq{When using smtp mode, you must specify a mailserver!\n}; 2013 2272 } 2014 my $mailuser = $opt{ $curr}{mailuser} || $arg{mailuser};2273 my $mailuser = $opt{mailuser} || $arg{mailuser}; 2015 2274 if ($mailuser eq 'example') { 2016 2275 die qq{When using smtp mode, you must specify a mailuser!\n}; 2017 2276 } 2018 my $mailpass = $opt{ $curr}{mailpass} || $arg{mailpass};2277 my $mailpass = $opt{mailpass} || $arg{mailpass}; 2019 2278 if ($mailpass eq 'example') { 2020 2279 die qq{When using smtp mode, you must specify a mailpass!\n}; 2021 2280 } 2022 my $mailport = $opt{ $curr}{mailport} || $arg{mailport};2281 my $mailport = $opt{mailport} || $arg{mailport}; 2023 2282 2024 2283 ## Attempt to connect to the server … … 2069 2328 sub sortsub { ## no critic (ProhibitNestedSubs) 2070 2329 2071 my $sorttype = $opt{ $curr}{sortby} || $arg{sortby};2330 my $sorttype = $opt{sortby} || $arg{sortby}; 2072 2331 2073 2332 if ($arg{type} eq 'duration') { … … 2156 2415 $meancount++; 2157 2416 my $item = sprintf '(item %d, count is %d)', $f->{displaycount}, $f->{count}; 2158 push @mean => sprintf "%10s %-22s", pretty_size($f->{mean},1), $item;2417 push @mean => sprintf '%10s %-22s', pretty_size($f->{mean},1), $item; 2159 2418 $maxmean = $f->{displaycount} if $f->{displaycount} > $maxmean; 2160 2419 last if $arg{tempfile_limit} and $meancount >= $arg{tempfile_limit}; … … 2169 2428 $totalcount++; 2170 2429 my $item = sprintf '(item %d, count is %d)', $f->{displaycount}, $f->{count}; 2171 push @totaltemp => sprintf "%10s %-22s", pretty_size($f->{total},1), $item;2430 push @totaltemp => sprintf '%10s %-22s', pretty_size($f->{total},1), $item; 2172 2431 $maxtotal = $f->{displaycount} if $f->{displaycount} > $maxtotal; 2173 2432 last if $arg{tempfile_limit} and $totalcount >= $arg{tempfile_limit}; … … 2181 2440 { 2182 2441 last if ! defined $mean[$count] and ! defined $totaltemp[$count]; 2183 printf "%-s |", defined $mean[$count] ? $mean[$count] : '';2442 printf '%-s |', defined $mean[$count] ? $mean[$count] : ''; 2184 2443 printf "%s\n", defined $totaltemp[$count] ? $totaltemp[$count] : ''; 2185 2444 $count++; … … 2373 2632 my $olen = $len; 2374 2633 my $waschopped = 0; 2375 my $maxsize = defined $opt{ $curr}{statement_size}2376 ? $opt{ $curr}{statement_size}2634 my $maxsize = defined $opt{statement_size} 2635 ? $opt{statement_size} 2377 2636 : $arg{statement_size}; 2378 2637 … … 2401 2660 $arg{debug} and warn " Performing final cleanup\n"; 2402 2661 2403 ## If offset has changed, save it 2404 my $newoffset = 0; 2405 if (exists $opt{$curr}{newoffset}) { 2406 $changes++; 2407 $newoffset = $opt{$curr}{newoffset}; 2408 $arg{verbose} and warn " Setting offset to $newoffset\n"; 2409 } 2410 2411 ## Reset always rewrites the file, even in dryrun mode 2412 if (($changes and !$arg{dryrun}) or $arg{reset}) { 2413 $arg{verbose} and warn " Saving new config file (changes=$changes)\n"; 2414 open my $fh, '>', $configfile or die qq{Could not write "$configfile": $!\n}; 2415 my $oldselect = select $fh; 2416 my $now = localtime; 2417 print qq{## Config file for the tail_n_mail program 2662 ## Need to walk through and see if anything has changed so we can rewrite the config 2663 ## For the moment, that only means the offset and the lastfile 2664 2665 ## Have we got new lastfiles or offsets? 2666 for my $t (@{ $opt{file} }) { 2667 if ($t->{latest} ne $t->{lastfile}) { 2668 $changes++; 2669 $t->{lastfile} = delete $t->{latest}; 2670 } 2671 if (exists $opt{newoffset}{$t->{lastfile}}) { 2672 my $newoffset = $opt{newoffset}{$t->{lastfile}}; 2673 if ($t->{offset} != $newoffset) { 2674 $changes++; 2675 $t->{offset} = $newoffset; 2676 } 2677 } 2678 } 2679 2680 ## No rewriting if in dryrun mode, but reset always trumps dryrun 2681 ## Otherwise, do nothing if there have been no changes 2682 return if (!$changes or $arg{dryrun}) and !$arg{reset}; 2683 2684 $arg{verbose} and warn " Saving new config file\n"; 2685 open my $fh, '>', $configfile or die qq{Could not write "$configfile": $!\n}; 2686 my $oldselect = select $fh; 2687 my $now = localtime; 2688 print qq{## Config file for the tail_n_mail program 2418 2689 ## This file is automatically updated 2419 2690 ## Last updated: $now 2420 2691 }; 2421 2692 2422 for my $item (qw/ email from type duration tempfile find_line_number sortby duration_limit tempfile_limit/) { 2423 next if ! exists $opt{$curr}{$item}; 2424 2425 next if $item eq 'duration' and $arg{duration} < 0; 2426 next if $item eq 'duration_limit' and ! $arg{duration_limit}; 2427 2428 next if $item eq 'tempfile' and $arg{tempfile} < 0; 2429 next if $item eq 'tempfile_limit' and ! $arg{tempfile_limit}; 2430 2431 ## Only rewrite if it came from this config file, not tailnmailrc or command line 2432 next if ! exists $opt{configfile}{$item}; 2433 add_comments(uc $item); 2434 if (ref $opt{$curr}{$item} eq 'ARRAY') { 2435 for my $itemz (@{$opt{$curr}{$item}}) { 2436 next if ! exists $opt{configfile}{"$item.$itemz"}; 2437 printf "%s: %s\n", uc $item, $itemz; 2438 } 2439 } 2440 else { 2441 printf "%s: %s\n", uc $item, $opt{$curr}{$item}; 2442 } 2443 } 2444 if ($opt{configfile}{maxsize}) { 2445 print "MAXSIZE: $opt{$curr}{maxsize}\n"; 2446 } 2447 if ($opt{$curr}{customsubject}) { 2448 add_comments('MAILSUBJECT'); 2449 print "MAILSUBJECT: $opt{$curr}{mailsubject}\n"; 2450 } 2451 2452 print "\n"; 2453 add_comments('FILE'); 2454 if (! $opt{$curr}{inherited_filename}) { 2455 print "FILE: $opt{$curr}{original_filename}\n"; 2456 } 2457 print "LASTFILE: $opt{$curr}{filename}\n"; 2458 print "OFFSET: $newoffset\n"; 2459 for my $inherit (@{$opt{$curr}{inherit}}) { 2460 add_comments("INHERIT: $inherit"); 2461 print "INHERIT: $inherit\n"; 2462 } 2463 for my $include (@{$opt{$curr}{include}}) { 2464 next if ! exists $opt{configfile}{"include.$include"}; 2465 add_comments("INCLUDE: $include"); 2466 print "INCLUDE: $include\n"; 2467 } 2468 for my $exclude (@{$opt{$curr}{exclude}}) { 2469 next if ! exists $opt{configfile}{"exclude.$exclude"}; 2470 add_comments("EXCLUDE: $exclude"); 2471 print "EXCLUDE: $exclude\n"; 2472 } 2473 print "\n"; 2474 2475 select $oldselect; 2476 close $fh or die qq{Could not close "$configfile": $!\n}; 2477 } 2693 for my $item (qw/ log_line_prefix email from type mailsig duration tempfile find_line_number sortby duration_limit tempfile_limit/) { 2694 next if ! exists $opt{$item}; 2695 2696 next if $item eq 'duration' and $arg{duration} < 0; 2697 next if $item eq 'duration_limit' and ! $arg{duration_limit}; 2698 2699 next if $item eq 'tempfile' and $arg{tempfile} < 0; 2700 next if $item eq 'tempfile_limit' and ! $arg{tempfile_limit}; 2701 2702 ## Only rewrite if it came from this config file, not tailnmailrc or command line 2703 next if ! exists $opt{configfile}{$item}; 2704 add_comments(uc $item); 2705 if (ref $opt{$item} eq 'ARRAY') { 2706 for my $itemz (@{$opt{$item}}) { 2707 next if ! exists $opt{configfile}{"$item.$itemz"}; 2708 printf "%s: %s\n", uc $item, $itemz; 2709 } 2710 } 2711 else { 2712 printf "%s: %s\n", uc $item, $opt{$item}; 2713 } 2714 } 2715 2716 if ($opt{configfile}{maxsize}) { 2717 print "MAXSIZE: $opt{maxsize}\n"; 2718 } 2719 if ($opt{customsubject}) { 2720 add_comments('MAILSUBJECT'); 2721 print "MAILSUBJECT: $opt{mailsubject}\n"; 2722 } 2723 2724 print "\n"; 2725 for my $inherit (@{$opt{inherit}}) { 2726 add_comments("INHERIT: $inherit"); 2727 print "INHERIT: $inherit\n"; 2728 } 2729 for my $include (@{$opt{include}}) { 2730 next if ! exists $opt{configfile}{"include.$include"}; 2731 add_comments("INCLUDE: $include"); 2732 print "INCLUDE: $include\n"; 2733 } 2734 for my $exclude (@{$opt{exclude}}) { 2735 next if ! exists $opt{configfile}{"exclude.$exclude"}; 2736 add_comments("EXCLUDE: $exclude"); 2737 print "EXCLUDE: $exclude\n"; 2738 } 2739 for my $exclude_prefix (@{$opt{exclude_prefix}}) { 2740 next if ! exists $opt{configfile}{"exclude_prefix.$exclude_prefix"}; 2741 add_comments("EXCLUDE_PREFIX: $exclude_prefix"); 2742 print "EXCLUDE_PREFIX: $exclude_prefix\n"; 2743 } 2744 2745 print "\n"; 2746 add_comments('FILE'); 2747 for my $f (sort { $a->{suffix} <=> $b->{suffix} } 2748 @{ $opt{file} }) { 2749 2750 ## Skip inherited files 2751 next if exists $f->{inherited}; 2752 2753 printf "\nFILE%d: %s\n", $f->{suffix}, $f->{original}; 2754 2755 ## Got any lastfile or offset for these? 2756 if ($f->{lastfile}) { 2757 printf "LASTFILE%d: %s\n", $f->{suffix}, $f->{lastfile}; 2758 } 2759 ## The offset may be new, or we may be the same as last time 2760 if (exists $opt{newoffset}{$f->{lastfile}}) { 2761 printf "OFFSET%d: %d\n", $f->{suffix}, $opt{newoffset}{$f->{lastfile}}; 2762 } 2763 elsif ($f->{offset}) { 2764 printf "OFFSET%d: %d\n", $f->{suffix}, $f->{offset}; 2765 } 2766 } 2767 print "\n"; 2768 2769 select $oldselect; 2770 close $fh or die qq{Could not close "$configfile": $!\n}; 2478 2771 2479 2772 return;
