#! /usr/bin/perl ## Warning: this is a BETA-3 / 950816. ## ## This is a Perl version for ``innwatch''. ## ## The original innwatch package, furnished with the INN distribution has ## been written, extended and modified by Mike Cooper, Robert Elz and ## Steve Groom as well as others. ## ## Perl version written and maintained for all those systems who have ## broken shells by Christophe Wolfhugel (formerly ## working at Herve Schauer Consultants). ## of the Pasteur Institute, Paris, France. ## You might also find this script useful as it sucks much much less CPU ## than its shell equivalent, most functions are Perl builtins and ease ## the work. ## ## Copyright (C) 1993, Christophe Wolfhugel & Herve Schauer Consultants. ## Copyright (C) 1995, Christophe Wolfhugel & Institut Pasteur. ## ## This is free software. Don't sell it. Don't pay to get it. ## Don't distribute this package modified. Send me diffs for inclusion instead. ## ## As with all free software: ## ## ABSOLUTELY NO WARRANTY WITH THIS PACKAGE. USE IT AT YOUR OWN RISKS. ## ##----------------------------------------------------------------------------- ## ## This release as well as any new ones can be retrieved by anonymous ftp ## on ftp.univ-lyon1.fr as ## /pub/systems/unix/news/transport/inn/contrib/innwatch*.pl ## or by using our ftpmail server: ftpmail@grasp.insa-lyon.fr. ## ##----------------------------------------------------------------------------- ## ## INSTALLATION: subst -f config.data innwatch.pl before starting the ## daemon. ## ## Following stuff gets subst'ed to have the path names suitable for your ## site. ## =()<$ctlwatch = '@<_PATH_CTLWATCH>@';>()= print("You did not run subst -f config.data innwatch.pl. Exiting\n"); exit(0); ## =()<$daily = '@<_PATH_LOCKS>@/LOCK.news.daily';>()= $daily = '/local/news/LOCK.news.daily'; ## =()<$innwstatus = '@<_PATH_INNWSTATUS>@';>()= $innwstatus = '/local/news/innwatch.status'; ## =()<$mailcmd = '@<_PATH_MAILCMD>@';>()= $mailcmd = '/usr/bin/mail'; ## =()<$most_logs = '@<_PATH_MOST_LOGS>@';>()= $most_logs = '/var/log/news'; ## =()<$newsmaster = '@@';>()= $newsmaster = 'newsmaster'; ## =()<$locks = '@<_PATH_LOCKS>@';>()= $locks = '/local/news'; ## =()<$serverpid = '@<_PATH_SERVERPID>@';>()= $serverpid = '/local/news/innd/innd.pid'; ## =()<$spool = '@<_PATH_SPOOL>@';>()= $spool = '/news'; ## =()<$sleeptime = @@;>()= $sleeptime = 5; ## =()<$watchpid = '@<_PATH_WATCHPID>@';>()= $watchpid = '/local/news/innwatch.pid'; ## Set the path ## =()<$ENV{'PATH'} = "@<_PATH_NEWSBIN>@:$ENV{'PATH'}";>()= $ENV{'PATH'} = "/usr/local/news/bin:$ENV{'PATH'}"; $progname = "innwatch"; ### Where to put the timestamp file (directory and filename). $timestamp = "$locks/$progname.time"; $lock = "$locks/LOCK.$progname"; ### Logfile to watch. Comment out if no logwatch. $logfile = "$most_logs/news.crit"; ## Args for compatibility with the shell verion. But I did not test the ## script with arguments yet, so it is expected to work, but not guaranteed. while ($_ = $ARGV[0], /^-/) { shift; /^-f$/ && ((@ARGV > 0) || die "Missing argument to \"-f\" option.\n") && ($ctlwatch = shift) && next; /^-f(.+)$/ && ($ctlwatch = $1) && next; /^-l$/ && ((@ARGV > 0) || die "Missing argument to \"-l\" option.\n") && ($logfile = shift) && next; /^-l(.+)$/ && ($logfile = $1) && next; /^-t$/ && ((@ARGV > 0) || die "Missing argument to \"-t\" option.\n") && ($sleeptime = shift) && next; /^-t(.+)$/ && ($sleeptime = $1) && next; /^-t$/ && ((@ARGV > 0) || die "Missing argument to \"-t\" option.\n") && ($sleeptime = shift) && next; /^-t(.+)$/ && ($sleeptime = $1) && next; /^-mailonchange$/ && ($mailonchange = 1) && next; die "Unknown option \"$_\".\n"; } ## Lock our stuff if (system("shlock -p $$ -f $lock") !=0) { open(F1, "$lock"); $_ = ; close(F1); chop; die("$progname: [$$] locked by [$_]"); } ## Load the innwatch.ctl file, interesting parts only. This is much much ## faster than with the shell release as this is done once only at startup. sub loadctl { $ctlmtime = (stat($ctlwatch))[9]; return if ($ctlmtime == $oldctlmtime); $oldctlmtime = $ctlmtime; @ctline = (); open(F1, "$ctlwatch") || die("Can't open $ctlwatch"); while () { next if (/^\s*$/ || /^\s*#/); chop; push(@ctline, $_); } close(F1); } $SIG{'INT'} = 'IGNORE'; sub handler1 { unlink($lock); unlink($watchpid); exit(1); } $SIG{'HUP'} = 'handler1'; $SIG{'QUIT'} = 'handler1'; $SIG{'TERM'} = 'handler1'; open(F1, "> $watchpid"); print F1 "$$\n"; close(F1); ### The reason why we turned INND off, and its, and our current state. $reason = ''; $innd = ''; $state = ''; sub handler2 { open(F1, "> $innwstatus"); print F1 "$progname waiting for INND to start (pid: $$)\n"; print F1 `date`; close(F1); } $SIG{'INT'} = 'handler2'; ### We need to remember the process ID of innd, in case one exits ### But we need to wait for innd to start before we can do that while (! $pid) { if (!open(F1, "$serverpid")) { sleep($sleeptime); next; } $pid = ; close(F1); chop($pid); } sub handler3 { open(F1, "> $innwstatus"); if ($state eq '') { print F1 "$progname state RUN interval $sleeptime pid $$\n"; } else { print F1 "$progname state $state interval $sleeptime pid $$\n"; } if ($innd eq '') { $x = "GO"; } else { $x = $innd; } $x = "$x: $reason" if ($reason ne ''); print F1 "INND state $x\n"; print F1 `date`; close(F1); } $SIG{'INT'} = 'handler3'; chdir($spool); $nextsleep = 1; $hasexited = "false"; while (sleep($nextsleep) && wait) { &loadctl; $nextsleep = $sleeptime; ## If news.daily is running, idle: we don't want to change the ## status of anything while news.daily may be depending on what we ## have done. next if (-f "$daily"); ## Check to see if INND is running. ## Notify NEWSMASTER if it has stopped or just restarted. if (system("ctlinnd -s -t 120 mode 2>/dev/null") == 0) { if ($hasexited eq "true") { $hasexited = "false"; open(F1, "| $mailcmd -s \"INND is now running\" $newsmaster"); close(F1); } } else { if ($hasexited eq "false") { $hasexited = "true"; open(F1, "| $mailcmd -s \"INND is NOT running\" $newsmaster"); close(F1); } next; } ## If innd has exited & restarted, put the new one into the ## same state the old one was in if (open(F1, "$serverpid")) { $npid = ; close(F1); chop($npid); if ($pid != $npid) { system("ctlinnd -s \"$innd\" \"$reason\"") if ($innd ne "go"); $pid = $npid; } } $value = 0; $prevexp = ''; $l = 0; foreach $line (@ctline) { $l++; $delim = $1 if ($line =~ /^(.)/); ## Parse the line into seven fields, and assign them to local vars. ## You're welcome to work out what's going on with quoting in ## the next few lines if you feel inclined. @line = split(/\s*$delim\s*/, $line); next if ($#line != 7); $lab = $line[1]; $cnd = $line[2]; $exp = $line[3]; $tst = $line[4]; $lim = $line[5]; $cmd = $line[6]; $cmt = $line[7]; ## Change shell tests to perl tests $tst = "==" if ($tst eq "eq") ; $tst = "!=" if ($tst eq "ne") ; $tst = "<" if ($tst eq "lt") ; $tst = ">" if ($tst eq "gt") ; $tst = "<=" if ($tst eq "le") ; $tst = ">=" if ($tst eq "ge") ; ## If there's no label, the label is the line number. $lab = $l if ($lab eq ""); ## Should we act on this line? We will if one (or more) of the ## specified conditions is satisfied. $yeah = 0; if ($cnd eq '') { next if ($state && $state ne $lab); } else { foreach $c (split(/\s+/, $cnd)) { if ($c eq '+') { next if ($state); } elsif ($c eq '*') { ; } elsif ($c eq '-*') { next if ($state eq $c); } else { next if ($state ne $c); } $yeah = 1; last; } next if ($yeah == 0); } ## Evaluate the expression, if there is one, and if that works. if ($exp && $exp ne $prevexp) { $value = `$exp`; chop($value); if ($? == 0) { if ($cmd =~ /^(throttle|pause)$/) { $ok = "n"; } else { $ok = "y"; } if (($state eq '' || $state ne $lab || $ok eq "y") && eval "$value $tst $lim") { $r = "$cmt [$progname:$lab] $value $tst $lim"; $o = ''; if ($cmd eq "throttle") { if ($state eq "" || $state eq "go") { $reason = $r; } $o = $lab; $arg = $reason; } elsif ($cmd eq "pause") { $o = $lab; $reason = $r; $arg = $reason; } elsif ($cmd eq "shutdown") { $arg = $r; } elsif ($cmd eq "flush") { $arg = ''; $o = $state; $arg = $reason; } elsif ($cmd eq "go") { $arg = $reason; $nextsleep = 1; $reason = ''; } elsif ($cmd eq "exit") { exit(0); } if (system("ctlinnd -s \"$cmd\" \"$arg\"") == 0) { $state = $o; $innd = $cmd; if ($mailonchange) { open(F1, "| $mailcmd -s \"INN: $cmd\" $newsmaster"); print F1 "$arg\n"; close(F1); } } last; } elsif ($state eq $lab && ($cmd eq "throttle" || $cmd eq "pause") && eval "! ($value $tst $lim)") { system("ctlinnd -s go \"$reason\""); $state = ''; $reason = ''; $innd = ''; ## If we have started innd, run all tests again quickly in ## case there is some other condition that should stop it. $nextsleep = 1; last; } } } } ## Foreach line if ($logfile && -f "$logfile") { if (! -f "$timestamp") { $doit = $logfile; } else { # use stat to print most recently modified file first. # If that's ${LOGFILE}, it's changed since the last pass. if ((stat($logfile))[9] > (stat($timestamp))[9]) { $doit = $logfile; } else { $doit = ''; } } # If the file has been modified more recently than the timestamp, # and the file has length greater than 0, send the warning. if ($doit ne '' && -s "$logfile") { open(F1, "> $timestamp"); print F1 `date`; close(F1); open(F1, "| $mailcmd -s \"$progname warning: messages in $logfile\" $newsmaster"); print F1 "-----\n"; print F1 `ctlinnd -t120 mode`, "\n"; print F1 "-----\n"; print F1 `cat $logfile`; close(F1); } } } unlink($lock); exit(0);