#!/usr/bin/env perl
BEGIN {
if
(!${^TAINT}) {
exec
$^X,
'-T'
,
'--'
, $0,
@ARGV
or
die
"exec '$^X': $!"
;
}
else
{
if
(
my
$lib
=
$ENV
{PERL5LIB}) {
my
@paths
=
$lib
=~ m{([^:]+)(?::|\z)}sg;
unshift
@INC
,
@paths
;
}
}
}
our
(
$mydir
,
$myname
);
BEGIN {
my
$location
= (-l $0) ? abs_path($0) : $0;
$location
=~ /(.*?)([^\/]+?)_?\z/s or
die
"?"
;
(
$mydir
,
$myname
) = ($1, $2);
}
$ENV
{PATH} = untainted
$ENV
{PATH};
our
@tailcmd
=
qw(tail --follow=name --retry --lines=0)
;
sub
usage {
print
"usage:
$myname
path/to/config.pl
config.pl should
return
(have as its
last
expression) a hash
with
these
keys
:
+{
logfile
=>
'path/to/file'
,
match
=>
sub
{
my
(\
$line
) = \
@_
; \
$line
=~ /some_regex/ },
collecttime
=> 1200,
report
=> report_mailto(
'system\@example\.com'
,
'admin\@example.com'
),
}
Runs `
@tailcmd
-- \$\
$config
{logfile}`,
collects all lines that match the
given
predicate, and
after
\$\
$config
{collecttime} seconds have passed since the first such
line, passes a file path containing the lines to the subroutine in
\$\
$config
{report};
'report_mailto(\$from, \$to)'
returns such a
sub
that then mails the contents of that file to the
given
\
$to
email
as the sender.
";
exit
1;
}
our
$verbose
= 0;
GetOptions(
"verbose"
=> \
$verbose
,
"help"
=>
sub
{usage},) or
exit
1;
usage
unless
@ARGV
== 1;
our
(
$configpath
) =
@ARGV
;
sub
fh_to_linestream (
$fh
,
$close
) {
fh_to_stream
$fh
, the_method(
"xreadline"
),
$close
}
sub
sleep_until(
$unixtime
) {
my
$t
=
time
;
my
$seconds
=
$unixtime
-
$t
;
if
(
$seconds
> 0.01) {
sleep
$seconds
;
tail sleep_until(
$unixtime
);
}
}
sub
forked (&) {
my
(
$thunk
) =
@_
;
if
(
my
$pid
= xfork) {
$pid
}
else
{
&$thunk
();
exit
0;
}
}
sub
safe_for_mail(
$str
) {
$str
=~ /^([^\r\n\t]*)\z/s ? $1 :
die
"not safe for mail: "
. show(
$str
);
}
sub
report_mailto (
$from
,
$to
) {
my
$_from
= safe_for_mail(
$from
);
my
$_to
= safe_for_mail(
$to
);
sub
(
$path
) {
my
$sendmail
= Chj::IO::Command->new_receiver(
"sendmail"
,
"-t"
);
my
$in
= xopen_read(
$path
);
$sendmail
->xprintln(
"From: $_from"
);
$sendmail
->xprintln(
"To: $_to"
);
$sendmail
->xprintln(
"Subject: "
. safe_for_mail(
$myname
) .
" "
. safe_for_mail(
$configpath
));
$sendmail
->xprintln;
$sendmail
->xprintln(
"$0 on "
. hostname() .
" found the following log messages:"
);
$sendmail
->xprintln;
$sendmail
->xflush;
$in
->xsendfile_to(
$sendmail
);
$in
->xclose;
$sendmail
->xxfinish;
unlink
$path
;
}
}
sub
require_config(
$path
) {
my
$arg
= untainted(
$path
=~ m|^\.{0,2}/| ?
$path
:
"./$path"
);
require
$arg
}
my
$config
= require_config
$configpath
;
lock_hash
%$config
;
my
$REPORTMSG
=
"REPORT-"
.
rand
();
my
(
$r
,
$w
) = xpipe;
sub
xtmpfile_noautoclean () {
my
$t
= xtmpfile;
$t
->autoclean(0);
$t
}
sub
processlines_ (
$lines
,
$out
,
$maybe_reporterpid
) {
weaken
$_
[0];
my
(
$line
,
$rest
) =
$lines
->first_and_rest;
warn
"line='$line', maybe_reporterpid="
. singlequote(
$maybe_reporterpid
)
if
$verbose
;
if
(
$line
=~ /^
$REPORTMSG
/) {
warn
"REPORT!"
if
$verbose
;
$out
->xclose;
$$config
{report}->(
$out
->path);
xxwaitpid
$maybe_reporterpid
;
tail processlines_(
$rest
, xtmpfile_noautoclean,
undef
)
}
else
{
if
(
$$config
{match}->(
$line
)) {
my
$t_report
=
time
+
$$config
{collecttime};
$out
->xprint(
$line
);
tail processlines_(
$rest
,
$out
,
$maybe_reporterpid
// forked {
$r
->xclose;
sleep_until
$t_report
;
$w
->xprintln(
$REPORTMSG
);
$w
->xclose;
warn
"sent $REPORTMSG"
if
$verbose
;
}
);
}
else
{
tail processlines_(
$rest
,
$out
,
$maybe_reporterpid
)
}
}
}
sub
processlines(
$lines
) {
weaken
$_
[0];
processlines_(
$lines
, xtmpfile_noautoclean,
undef
)
}
my
$tailpid
= forked {
$r
->xclose;
$w
->xdup2(1);
xexec
@tailcmd
,
"--"
,
$$config
{logfile};
};
my
$lines
= fh_to_linestream(
$r
,
sub
(
$fh
) {
$fh
->xclose;
xxwait
$tailpid
;
}
);
processlines
$lines
;