use strict;
use re 'taint';
use POSIX qw(strftime);
use BerkeleyDB;
use vars qw($VERSION); $VERSION = 1.02;
my($idlettl) = 60*60; my($activettl) = 10*60;
my($db_home) = '/var/amavis/db'; my($dbfile) = 'nanny.db';
my($wakeuptime) = 2;
sub fmt_age($$) {
my($t,$char) = @_;
my($bar);
$bar = substr(($char x 9 . ':') x 3 . $char x 5 . '>', 0, $t) if $char ne '';
my($s) = $t % 60; $t = int($t/60);
my($m) = $t % 60; $t = int($t/60);
my($h) = $t % 24; $t = int($t/24);
my($d) = $t;
my($str) = sprintf("%d:%02d:%02d", $h,$m,$s);
$str = (!$d ? " " : sprintf("%dd",$d)) . $str;
$str . ' ' . $bar;
};
$SIG{INT} = sub { die "\n" }; my($env) = BerkeleyDB::Env->new(
'-Home'=>$db_home, '-Flags'=> DB_INIT_CDB | DB_INIT_MPOOL);
defined $env or die "BDB no env: $BerkeleyDB::Error $!";
my($db) = BerkeleyDB::Hash->new(
'-Filename'=>$dbfile, '-Env'=>$env );
defined $db or die "BDB no dbN 1: $BerkeleyDB::Error $!";
my($cursor);
my(%waittime);
for (;;) {
$| = 0;
print "\n";
my(%proc_timestamp, %proc_task_id);
my($stat,$key,$val); my($now);
my($eval_stat,$interrupt); $interrupt = '';
{ my($h1) = sub { $interrupt = $_[0] };
local(@SIG{qw(INT HUP TERM TSTP QUIT ALRM USR1 USR2)}) = ($h1) x 8;
eval {
$cursor = $db->db_cursor; defined $cursor or die "db_cursor error: $BerkeleyDB::Error";
$now = time;
my($now_utc_iso8601) = strftime("%Y%m%dT%H%M%S",gmtime($now));
while ( ($stat=$cursor->c_get($key,$val,DB_NEXT)) == 0 ) {
if ($val !~ /^(\d+) (.*?) *\z/s) {
print STDERR "Bad db entry: $key, $val\n";
} else {
($proc_timestamp{$key}, $proc_task_id{$key}) = ($1,$2);
}
}
$stat==DB_NOTFOUND or die "c_get: $BerkeleyDB::Error $!";
$cursor->c_close==0 or die "c_close error: $BerkeleyDB::Error";
$cursor = undef;
};
my($eval_stat) = $@;
if (defined $db) {
$cursor->c_close if defined $cursor; $cursor = undef;
}
}
if ($interrupt ne '') { kill($interrupt,$$) } elsif ($eval_stat ne '') { chomp($eval_stat); die "BDB $eval_stat\n" }
my(@to_be_removed,@killed);
for my $pid (sort {$a<=>$b} keys %proc_timestamp) {
my($idling) = $proc_task_id{$pid} =~ /^\.?\z/s;
my($ttl) = $idling ? $idlettl : $activettl;
if (!kill(0,$pid)) { printf("PID %s: %-11s went away %s\n",
$pid, $proc_task_id{$pid},
fmt_age($now-$proc_timestamp{$pid}, $idling?'.':'=') );
push(@to_be_removed, $pid);
} elsif ($now <= $proc_timestamp{$pid}+$ttl) { printf("PID %s: %-11s %s\n",
$pid, $proc_task_id{$pid},
fmt_age($now-$proc_timestamp{$pid}, $idling?'.':'=') );
} else { printf("PID %s: %-11s terminated %s\n",
$pid, $proc_task_id{$pid},
fmt_age($now-$proc_timestamp{$pid}, $idling?'.':'=') );
if (kill('TERM',$pid)) { push(@killed,$pid) }
else { warn "Can't SIGTERM $pid: $!" }
}
}
if (@to_be_removed) {
local($SIG{'INT'}) = 'IGNORE';
$cursor = $db->db_cursor(DB_WRITECURSOR); defined $cursor or die "BDB db_cursor error: $BerkeleyDB::Error";
for my $key (@to_be_removed) {
my($val); my($stat) = $cursor->c_get($key,$val,DB_SET);
$stat==0 || $stat==DB_NOTFOUND
or die "BDB c_get: $BerkeleyDB::Error, $!.";
if ($stat==0) { $cursor->c_del==0 or die "BDB c_del: $BerkeleyDB::Error, $!.";
}
}
$cursor->c_close==0 or die "BDB c_close error: $BerkeleyDB::Error";
$cursor = undef;
}
my($delay) = 1; while (@killed) {
sleep $delay; $delay = 2;
for my $pid (@killed) {
$waittime{$pid}++;
printf("PID %s: sending SIGKILL in %d s\n", $pid, 30 - $waittime{$pid});
if ($waittime{$pid} > 30) { kill('KILL',$pid) or warn "Can't SIGKILL $pid: $!";
$waittime{$pid} = 0;
} elsif (!kill(0,$pid)) { printf("PID %s: %-11s successfully killed\n", $pid);
delete($waittime{$pid});
$pid = undef;
}
}
@killed = grep {defined} @killed;
printf("Waiting for the process to terminate: %s\n",
join(', ',@killed)) if @killed;
}
$| = 1;
sleep $wakeuptime;
}
END {
if (defined $db) {
$cursor->c_close if defined $cursor; $db->db_close==0 or die "BDB db_close error: $BerkeleyDB::Error $!";
}
print STDERR "exited\n";
}