#!/usr/bin/perl
#
# Spong client monitoring script.  This runs various tests locally on a machine
# and sends those results to the Spong server machine where the information
# is summarized and displayed.  This script currently checks the following:
#
#    * disk  (check to make sure that disks are not filling up)
#    * cpu   (check to make sure the load is reasonable)
#    * jobs  (check to make sure certain key processes are running)
#    * msgs  (scan through logs looking for problems)
#
# History:
# (1) Ported bb-local.sh script to perl. (Ed Hill Feb 26, 1997)
#
# $Id: spong-client.pl,v 1.20 2002/01/17 17:02:12 sljohnson Exp $

use lib '/usr/share/spong';

use Sys::Hostname;
use Socket;
use POSIX;
use Getopt::Long;

use Spong::Daemon;
use Spong::Status qw(status);
use Spong::Log;

$0 = "spong-client";  # Change the ps arguments section early

srand( time() ^ ($$ + ($$ << 15 )) );

$debug = $restart = $kill = $nosleep = 0;

if ( ! GetOptions("debug:i" => \$debuglevel, "restart" => \$restart, 
          "kill" => \$kill, "nosleep|refresh" => \$nosleep,
          "nodaemonize" => \$nodaemonize ) ) {
   &usage();
   exit 1;
}

# Initial debugging for preconfiguration debugging
Spong::Log::set_debug_context( 'debuglevel' => $debuglevel );

$me        = "/usr/bin/spong-client";
$conf_file = $ARGV[0] || "/etc/spong/spong.conf";
($HOST)    = gethostbyname(&Sys::Hostname::hostname());
$HOST      =~ tr/A-Z/a-z/;

$CHECKS = "";

&load_config_files(); # Loads the user specified configuration information
&init_logging();      # Initialize logging contexts
Spong::Daemon::Daemonize  # Daemonize if not signalling or debugging
  unless ($restart || $kill || $nosleep || $debug || $nodaemonize); 
&handle_signals();    # Set up handlers, and signal the current server if asked

# Find our SPONGSLEEP value

$SPONGSLEEP = $SPONGSLEEP{'spong-client'} || $SPONGSLEEP{'DEFAULT'} ||
              $SPONGSLEEP || 300;


%CHECKFUNCS = (); 
&load_checks();
&debug("Done loading check modules",1);

# Pretty simple program, check the stuff that we are looking at, report it to
# the server, sleep, and do it all again...

while( 1 ) {

   my ($check);
   foreach $check (split / /,$CHECKS) {
      $0 = "spong-client (checking $check)";
      eval { &{$CHECKFUNCS{$check}}(); };
      if ($@) { &error( "Error running check $check: $@") };
   }

   # If we are suppose to stay alive, then sleep about $SPONGSLEEP seconds, we
   # add a little randomness so that things don't get in sync and pound the 
   # spong-server.  Otherwise, just exit.

   if( $nosleep ) {
      last;
   } else {
      my $sleep = int($SPONGSLEEP - (.05 * $SPONGSLEEP) + 
		  rand(.1 * $SPONGSLEEP));
      &debug( "sleeping for $sleep seconds" );
      $0 = "spong-client (sleeping)";
      sleep $sleep;
   }
}

unlink( "/var/run/spong/spong-client.pid" ) unless $nosleep;
exit(0);


# ===========================================================================
# Utility functions, and signal handlers...
# ===========================================================================

sub usage {

   print qq
(Usage:
   $0  [--nodaemonize] [--debug n] [--nosleep|--refresh] [config_file]
   $0  --kill | --restart

   --debug n
         Run in the foreround and print debugging output
   --nodaemonize
         Run without becoming a daemon
   --nosleep
   --refresh
         Run one cycle of checks in the foreground and exit
   --restart
         Signal a running spong-client to restart.
   --kill
         Signal a running spong-client to terminate.
   config_file
         Use the named file as the configuration file
);
     
}


# This function initializes the debug and error logging contexts in the 
# Log module.

sub init_logging {
   if (defined $debuglevel) {
      $debug = ($debuglevel == 0) ? 1 : $debuglevel
   }

   Spong::Log::set_debug_context( 'debuglevel' => $debug );

   my $filename = ($SPONG_LOG_FILE) ? "/var/log/spong/spong-client.log" : "";
   my $syslog = ($SPONG_LOG_SYSLOG) ? 1 : 0;

   Spong::Log::set_error_context(  syslog   => $syslog,
                                   ident    => 'spong-client',
                                   logopt   => 'pid,cons',
                                   priority => 'ERR',
                                   filename => $filename,
                                 );

}


# Load our configuration variables, including anything specific to the host
# that we are running on.

sub load_config_files {
   require $conf_file || die "Can't load $conf_file: $!";
   if( -f "$conf_file.$HOST" ) {
      require "$conf_file.$HOST" || die "Can't load $conf_file.$HOST: $!";
   } else {
      my $tmp = (split( /\./, $HOST ))[0];
      if( -f "$conf_file.$tmp" ) { # for lazy typist
	 require "$conf_file.$tmp" || die "Can't load $conf_file.$tmp: $!";
      }
   }
   &debug( "configuration file(s) loaded" );
}

# This is part of the set up code, this sets up the signal handlers, and
# handles any command line arguments that are given to us that tell us to
# signal the current running spong-server program.

sub handle_signals {

   # Clear out signal mask in case we inherit any blocked sigs

   my $sigset = POSIX::SigSet->new;
   sigprocmask(SIG_SETMASK, $sigset );

   # Set up some signal handlers to handle our death gracefully, and also
   # listen for the HUP signal, and if we se that, we re-exec ourself.

   $SIG{'TERM'} = \&exit_handler;
   $SIG{'QUIT'} = \&exit_handler;
   $SIG{'HUP'}  = \&hup_handler;
   $SIG{'USR1'}  = \&hup_handler;
   $SIG{'PIPE'} = \&pipe_handler;

   # If the user gives us the --restart or --kill flags, then we signal the
   # currently running spong-client process, and tell it to either die, or
   # re-exec itself (which causes it to re-read it's configuration files.

   if( $restart || $kill ) {
      open( PID, "/var/run/spong/spong-client.pid") || die "Can't find pid: $!";
      my $pid = <PID>; chomp $pid;
      close PID;
      
      if( $restart ) { 
	 &debug( "telling pid $pid to restart" ); kill( 'HUP', $pid ); }
      if( $kill ) { 
	 &debug( "telling pid $pid to die" ); kill( 'QUIT', $pid );}
      
      exit(0);
   }

   # Check to see if we are already running 
   &already_running() unless $nosleep;

   # Write our pid to the spong tmp directory.
   
   system( "echo $$ >/var/run/spong/spong-client.pid" ) unless $nosleep;
}

# This routine check to see if another instance of spong-server is already
# running. If there is another instance, this instance will complain and die

sub already_running {
   # if there is a PID file
   if ( -f "/var/run/spong/spong-client.pid" ) {
      # Read the pid
      open( PID, "/var/run/spong/spong-client.pid" ) || die "Can't open pid: $!";
      my $pid = <PID>; chomp $pid;
      close PID;

      if ( kill 0,$pid ) {
         &error("Spong-client is already running as pid $pid");
         exit 1;
      }
   }
}



# Output functions, one for debugging information, the other for errors.

sub debug { Spong::Log::debug($_[0],$_[1]); }
sub error { Spong::Log::error($_[0]); }

# Signal handlers...

sub exit_handler { 
   &debug( "caught QUIT signal, exiting..." );
   unlink "/var/run/spong/spong-client.pid" if -f "/var/run/spong/spong-client.pid";
   exit(0);
}

sub hup_handler {
   &debug( "caught HUP signal, restarting..." );
   unlink "/var/run/spong/spong-client.pid" if -f "/var/run/spong/spong-client.pid";
   $SIG{$_[0]} = \&hup_handler;
   if( $debug ) { push(@args, "--debug", $debug); }
   if( $nodaemonize ) { push(@args, "--nodaemonize"); }
   exec $me, @args or die "Couldn't exec $me after HUP";
}

sub pipe_handler {
   wait();
   &debug( "caught $_[0] signal." );
   $SIG{$_[0]} = \&pipe_handler;
}

#
# Load client checking functions as defined the $HOSTS checks field
# Default to old checks if 'checks' is not present (cpu disk msgs jobs [local])

sub load_checks {

   if (! defined $CHECKS || ! $CHECKS ) {
      $CHECKS = 'disk cpu processes logs';
   };

   foreach $check (split / /,$CHECKS) {
      &debug( "Loading client check $check", 3 );
      eval "require 'Spong/Client/plugins/check_$check';";
      if ( $@ ) { &error( "Could not load $check check plugin: $@" ); }
   }

   if (defined &check_local) {
      &debug( "Loading client check local", 3 );
      $CHECKS .= ' local';
      $CHECKFUNCS{'local'} = \&check_local;
   }
}

