#! /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 <wolf@pasteur.fr> (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 = 'newsmaster';
##  =()<$locks = '@<_PATH_LOCKS>@';>()=
$locks = '/local/news';
##  =()<$serverpid = '@<_PATH_SERVERPID>@';>()=
$serverpid = '/local/news/innd/innd.pid';
##  =()<$spool = '@<_PATH_SPOOL>@';>()=
$spool = '/news';
##  =()<$sleeptime = @<INNWATCH_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");
   $_ = <F1>;
   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 (<F1>) {
      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 = <F1>;
   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 = <F1>;
      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);
