#!/usr/bin/perl
##############################################################################
#                                                                            #
# LittleBen V 0.5                                                            #
# client daemon for Big-Ben. Collect and send information to the server.     #
# by Cesare Pizzi                                                            #
#                                                                            #
# $Id: LittleBen,v 1.20 1998/01/15 19:53:57 cesare Exp cesare $              #
##############################################################################

$revision="0.5";
$internal_revision="1.20";

### Signal catch ###
$SIG{"HUP"}='trap_signal';
$SIG{"INT"}='trap_signal';
$SIG{"QUIT"}='trap_signal';
$SIG{"TERM"}='trap_signal';

### Idle time between two checks (seconds) ###
$pause=20;

### Port where Big-Ben is listening ###
$port=4455;

### System where Big-Ben is located ###
$them='localhost';

### Configuration file ###
$file_conf="LittleBen.conf";

### Log file name ###
$logfile="/tmp/LittleBen.log";
$log_event=1;

### Shell commands ###
$uptime="/usr/bin/uptime";
$ps="/bin/ps";
$ping="/bin/ping";

### Options for ps ###
$opts="-au";

print "LittleBen $revision - by Pizzi Cesare\n";
print "Type LittleBen -h to see the available options\n";

&parse_options();

### Read the LittleBen configuration ###
&read_conf($file_conf);

### Fork the process ###
if (($child=fork()) == 0) {

	### Son process ###

	### Unlink the process from the terminal ###
	setpgrp();

	#################
	### Main loop ###
	#################

	while (1) {

		sleep($pause);

		$packet=&collect_info();

		### Open the connection ###
		&connect_server($them,$port);

		##########################################################
		###                                                    ###
		### Send the packet with all the information to BigBen ###
		### The packet has the following format:               ###
		###                                                    ###
		### <process name>@@<number of proc>@@<message>###     ###
		###                                                    ###
		##########################################################
		print S $packet,"\n";

		### Close the connection ###
		shutdown(S,2);
	}

	exit(0);
}

exit(0);

##############################################################################
#                                                                            #
# SUBROUTINES                                                                #
#                                                                            #
##############################################################################

#######################################
### Connect the server to send data ###
#######################################
sub connect_server {

	### Get parameters ###
	local $them=shift;
	local $port=shift;

	###############################
	### Defines local variables ###
	###############################
	local $AF_INET, $SOCK_STREAM, $sockaddr, $name, $aliases;
	local $proto, $type, $len, $thisaddr, $thataddri, $hostname;
	
	$AF_INET=2;
	$SOCK_STREAM=1;

	$sockaddr='S n a4 x8';

	chop($hostname=`hostname`);

	############################################################
	### Collect info to build the connection with the server ###
	############################################################
	($name,$aliases,$proto)=getprotobyname('tcp');
	($name,$aliases,$port)=getservbyname($port,'tcp') unless $port=~/^\d+$/;
	($name,$aliases,$type,$len,$thisaddr)=gethostbyname($hostname);
	($name,$aliases,$type,$len,$thataddr)=gethostbyname($them);

	$this=pack($sockaddr,$AF_INET,0,$thisaddr);
	$that=pack($sockaddr,$AF_INET,$port,$thataddr);

	if (socket(S,$AF_INET,$SOCK_STREAM,$proto)) {
		### Nothing ###	
	} else {
		&log_message("ERROR: $!") if $log_event == 1;
		die;
	}

	if (bind(S,$this)) {
		### Nothing ###	
	} else {
		&log_message("ERROR: $!") if $log_event == 1;
		die;
	}

	### Call up the server ###
	if (connect(S,$that)) {
		&log_message("Connect to BigBen OK") if $log_event == 1;
	} else {
		`$ping -c 2 $them > /dev/null`;
		if ($? != 0) {
			&log_message("The server may be down or unreachable") if $log_event == 1;
		} else {
			&log_message("Unable to connect the server: BigBen is not running") if $log_event == 1;
		}

		die;
	}

	### Set the socket to be command buffered ###
	select(S);
	$|=1;
}

##########################################################################
### Read the configuaration file where are stored the process to check ###
##########################################################################
sub read_conf {

	### Get parameters ###
	local $file=shift;

	local $process,$num,$message;
	local $wrong_mex,$wrong_num;

	$wrong_mex="Wrong message set in configuration file. Exit.\n";
	$wrong_num="Wrong MIN,MAX values set in configuration file. Exit.\n";

	### Reset the associative arrays ###
	undef %min;
	undef %max;
	undef %message;

	### Open and read the file ###
	open(CONF,"$file") || die "Can't open $file: $!";

	while (<CONF>) {

		next if /^#/;           # Skip comment lines

		($process,$num,$message)=split(/\s+/,$_);

		### Check for correct values ###
		if (!($message=~/E|W/i)) {
			print "$wrong_mex";
			&log_message("$wrong_mex") if $log_event == 1;

			exit(1);
		}
		if (!($num=~/\d+,\d+/)) {
			print "$wrong_num";
			&log_message("$wrong_num") if $log_event == 1;

			exit(1);
		}

		### Load the arrays ###
		($min{"$process"},$max{"$process"})=split(/,/,$num);
		$message{"$process"}=$message;
	}

	close(CONF);
}

##################################
### Collect info on the system ###
##################################
sub collect_info {

	### Local variables ###
	local $chk,$proc_num;
	local $tmp_pack="";
	local $return;
	local $mex;

	foreach $chk (keys %message) {

		### Check the number of process ###
		$proc_num=&check_process($chk);	

		$mex="";

		### Check for bad situations ###
		if ($proc_num < $min{"$chk"} || $proc_num > $max{"$chk"}) {

			### Put in mex the proper message ###
			$mex=$message{"$chk"};
		}

		### Build the packet ###
		$tmp_pack.="$chk^^$proc_num^^$mex###";
	}

	### Delete stuff from string ###
	$tmp_pack=~s/\n//;

	$return=$tmp_pack;
}

####################################################################
### Return the number of running processes, or for special keys, ###
### return a number (load or users)                              ###
####################################################################
sub check_process {

	local $proc=shift;
	local $n=0;
	local $result;
	
	### Check for special keys ###
	if ($proc eq "USERS" || $proc eq "LOAD") {

		$result=`$uptime`;

		### Check for special keys ###
		if ($proc eq "USERS") {
			$result=~/(\d+)(\s+user)/i;
			$n=$1;
		} elsif ($proc eq "LOAD") {
			$result=~/(average.?\s+)(\d+\.\d+)/i;
			$n=$2;
		}
	} else {

		### Open a pipe with PS ###
		open(PS,"$ps $opts |") || &error($!);

		while (<PS>) {

			$n++ if /$proc/;
		}
		close(PS);
	}

	### Return the value ###
	$return=$n;
}

######################################
### Parse the command line options ###
######################################
sub parse_options {

	while ($_=$ARGV[0],s/^-( ?)(.+)$/$2/ && shift(@ARGV)) {

		next if $_ eq '';

		### Help screen ###
		if (/^h/) { &usage(); }

		### Set the pause between two check ###
		if (/^s(\d*)$/) {
			if ($1) {
				$pause=$1;
			} else {
				$pause=$ARGV[0];
				shift(@ARGV);
			}

			next;
		}

		### Set the port where BigBen is listening ###
		if (/^p(\d*)$/) {
			if ($1) {
				$port=$1;
			} else {
				$port=$ARGV[0];
				shift(@ARGV);
			}

			next;
		}

		### Set remote host where BigBen is ###
		if (/^r(.*)$/) {
			if ($1) {
				$them=$1;
			} else {
				$them=$ARGV[0];
				shift(@ARGV);
			}

			next;
		}

		### Set the configuration file ###
		if (/^c(.*)$/) {
			if ($1) {
				$file_conf=$1;
			} else {
				$file_conf=$ARGV[0];
				shift(@ARGV);
			}

			next;
		}

		### Set the log file ###
		if (/^l(.*)$/) {
			if ($1) {
				$logfile=$1;
			} else {
				$logfile=$ARGV[0];
				shift(@ARGV);
			}

			next;
		}

		### Set the ps options to show the process ###
		if (/^ps(.*)$/) {
			if ($1) {
				$opts=$1;
			} else {
				$opts=$ARGV[0];
				shift(@ARGV);
			}

			### Check for the '-' ###
			if (!($opts=~/^-/)) {
				$opts="-".$opts;
			}

			next;
		}

		### Disable logging ###
		if (/^nolog$/) {
			$log_event=0;

			next;
		}

		&usage();
	}
}

#################################################
### Handle the signal received by the program ###
#################################################
sub trap_signal {

	local ($sig)=@_;

	#################################
	### Check the received signal ###
	#################################

	if ($sig eq 'HUP') {

		&log_message("Signal received: HUP. Configuration file reloaded") if $log_event == 1;

		### Read the configuration file ###
		&read_conf($file_conf);

	} elsif ($sig eq 'INT' || $sig eq 'QUIT' || $sig eq 'TERM') {

		&log_message("Signal received: $sig. Exit") if $log_event == 1;
		exit(0);

	} else {
		### Unknown signal. Ignore it. ###
	}
}

###################################
### Display the program options ###
###################################
sub usage {

	print "Usage: LittleBen [options]\n\n";
	print "\t-s nnn     Seconds between two check (default: $pause)\n";
	print "\t-p nnn     Port where BigBen is listening (default: $port)\n";
	print "\t-r xxx     Remote host where BigBen is located (default: $them)\n";
	print "\t-c xxx     Configuration file (default: $file_conf)\n";
	print "\t-l xxx     Log file (default: $logfile)\n";
	print "\t-ps xxx    Option for ps to show processes (default: $opts)\n";
	print "\t-nolog     Disable logging\n";
	print "\t-h         This page\n";
	print "\n";

	exit(0);
}

#####################################################
### Log function. Open a log file and write in it ###
#####################################################
sub log_message {

	### Get the parameter ###
	local $message=shift;
	local $date;

	open(LOG,">>$logfile") || warn "Can't open log file: $!";

	$date=localtime;
	print LOG "$date: $message\n";

	close(LOG);
}
