#! /usr/bin/perl

# This script is a server for the
# cricket program.  It waits for
# connections from the client, gathers
# the necessary data and reports it back
# to the client.  It then closes the 
# socket connection and awaits another
# client connection.

# A simple example for gathering the
# data from the output of 'df -k' is
# included in this script as well as
# gathering the process size of specified
# processes.  This should be a good example 
# of how to add other commands.

use IO::Socket;
use FileHandle;
require "getopts.pl";

# Default variables
$passwd = "public";
$logged_in = 0;
$verbose = 0;
$not_silent = 0;
$wait_for_connect = 1;

# Change this logfile to a vaild path
$logfile = "/tmp/cricket_server.log";


# Try to open the Logfile
open(LOG, ">> $logfile") || 
  die "Could not open file $logfile: $!\n";

# Unbuffer the logfile
LOG->autoflush(1);


# Check for Signal interupts
foreach $x ( "INT", "QUIT", "TERM", "KILL" ) {
    $SIG{$x} = "termSignalHandler";
}


# Check for command line options
&Getopts('hlv');
if ($opt_h) { &usage; }
if ($opt_l) { $not_silent = 1; }
if ($opt_v) { $verbose = 1; }



$sock = new IO::Socket::INET (LocalHost => 'localhost',
                              LocalPort => '5500',
                              Proto     => 'tcp',
                              Listen    => '5',
                              Reuse     => '1',
                              );

die "Socket could not be created: $!\n" unless $sock;

&logmsg("Awaiting connection...");


while ($wait_for_connect) {



  while ($new_sock = $sock->accept() ) {


    # Find out who connected to us
    ($host) = $new_sock->peerhost();

    &logmsg("Connected to ($host)...");


    # Now wait for client commands
    while (defined ($buf = <$new_sock>) ) {

      # Get rid of the newline at the end of the buffer
      chomp($buf);
      if ($verbose) { &logmsg("  .. buf = $buf"); }
  
      # The command is delimited by a ':' character.
      @command = split(/:/, $buf);

      # Arguments are delimited by spaces
      @args = split(/ /, @command[1]);
  
      if ($verbose) {
        &logmsg("  .. command = @command[0]");
        &logmsg("  .. argument = @command[1]");
        &logmsg("  .. args = @args");
      }


      # Check for login
      if (@command[0] =~ /login/) {
  
        if ($logged_in == 0) {
          if (@command[1] eq $passwd) {
            &logmsg("Password accepted");
            &logmsg("Client logged in ($new_sock)");
            print $new_sock "password accepted\n";
            $logged_in = 1;
          } else {
            &logmsg("Incorrect password");
            print $new_sock "incorrect password\n";
            &logmsg("Closing socket");
            close ($new_sock);
          }
        } else {
          &logmsg("Received login from client already logged in");
          print $new_sock "already logged in\n";
        }
  
      } elsif (@command[0] =~ /logout/) {
  
        &logmsg("Client logged out ($new_sock)");
        close ($new_sock);
        $logged_in = 0;
  
      } elsif (@command[0] =~ /get_info/) {
  
	# Gather the data...
        $results = &parse_data(@args);
  
        &logmsg("Sending data to client ($results)");
        print $new_sock "$results\n";
  
      } else {
  
        &logmsg("Unknown command: @command[0]");
        print $new_sock "unknown command: @command[0]\n";
  
      }
  
      if ($logged_in) {
        if ($verbose) { &logmsg("  .. Waiting for next command"); }
      }
  
  
    }
  
    $logged_in = 0;
    &logmsg("Socket closed...");
    &logmsg("Awaiting next connection...");


  }

}

# Ugly, but useful...
cleanup_exit:

&logmsg("Closing socket...");
close($sock);

&logmsg("Closing logfile...");
close (LOG);

&logmsg("Exiting...");
exit;


# This subroutine takes the arguments sent
# to the server and parses them into like bins,
# Then the respective subroutine used to gather
# stats for each bin is called.
sub parse_data {

  my (@data) = @_;
  my (@disk_args) = ();
  my (@ps_mem_args) = ();
  my ($piece) = "";
  my ($total_results) = "";
  my ($results) = 0;

  # Split things up into their
  # respective bins.  Must also
  # go ahead and collect the data
  # as we come across it so it
  # can be reported back in the
  # correct order.

  foreach $piece (@data) {

    if ($piece =~ /disk,/) { 

      # Get rid of the heading for each command
      $piece =~ s/disk,//g; 
      push (@disk_args, $piece);

      &logmsg("Gathering disk data (@disk_args)");
      $results = &gather_disk_stats(@disk_args);
  
      $total_results = "$total_results,$results";

      undef @disk_args;

    } elsif ($piece =~ /ps_mem,/) {

      # Get rid of the heading for each command
      $piece =~ s/ps_mem,//g; 
      push (@ps_mem_args, $piece);

      &logmsg("Gathering ps mem data (@ps_mem_args)");
      $results = &gather_ps_mem_stats(@ps_mem_args);

      $total_results = "$total_results,$results";

      undef @ps_mem_args;

    } else {

      &logmsg("  Unknown data argument: $piece");

    }

  }

  # Get rid of the beginning ',' character if it
  # exists
  if ($total_results =~ /^,/) { $total_results =~ s/,//; }

  return ($total_results);

}


# This subroutine gathers the disk partition
# capacity as reported by 'df -k <partition>'
# as reported on the host system
sub gather_disk_stats {

  my (@partitions) = @_;
  my ($data) = "";
  my ($df_result) = 0;
  my (@df_result) = "";
  my ($line) = "";
  my (@line) = "";
  my ($count) = "";
  my ($df_cmd) = "/usr/sbin/df";


  $df_result = `$df_cmd -k @partitions`;

  @df_result = split(/\n/, $df_result);

  $count = 0;
  foreach $line (@df_result) {

    # First line is description, chuck it
    if ($count == 0) { $count++; next; }

    else {
      $line =~ tr / //s;
      @line = split(/ /, $line);
      chop(@line[4]);
      if ($data eq "") { $data = "@line[4]"; }
      else { $data = "$data,@line[4]"; }
    }

  }

  return $data;

}


# This subroutine gathers the process memory stats
# as reported by the /proc/<process pid>/as file
# as reported on the host system (UnixWare)
sub gather_ps_mem_stats {

  my (@ps_mem_args) = @_;
  my ($data) = "";
  my ($arg) = "";
  my ($ps_result) = "";
  my (@ps_result) = "";
  my ($vmps_cmd) = "/opt/wildfire/tools/vmps";
  my ($ps_cmd) = "/usr/bin/ps -ef";
  my ($size) = 0;


  foreach $process (@ps_mem_args) {

    $ps_result = "";

    $ps_result = `$ps_cmd | grep -v grep | grep -v debug | grep $process`;

    if ($ps_result eq "") { $data = "$data,0"; }
    else {
      $ps_result =~ tr / //s;
      @ps_result = split(/ /, $ps_result);
      $pid = @ps_result[2];
  
       # All of this info is done on the /proc/<pid>/as file on Unixware machines
       ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat "/proc/$pid/as";
  
       # Divide the size by 1024 so it is in kilobytes - not necessary
       #use integer;
       #$size = $size / 1024;
       #no integer;
  
      $data = "$data,$size";

    }
  

  }

  if ($data =~ /^,/) { $data =~ s/,//; }

  return $data;

}


sub termSignalHandler {

    my ( $sig ) = @_;

    &logmsg("Caught a SIG$sig -- shutting down");

    $wait_for_connect = 0;
    goto cleanup_exit;

  return;
}   

sub logmsg {

  my ($msg) = @_;
  #my ($dt) = &ctime(time());
  my ($dt) = scalar localtime;
  #chomp($dt);

  if ($not_silent) { print "$dt: $msg\n"; }
  print LOG "$dt: $msg\n";

}

sub usage {

  print "\n";
  print "Usage: cricket_server.pl [-h] [-l] [-v]\n";

  print "  -h = Help\n" ;
  print "  -l = Log messages to STDOUT\n" ;
  print "  -v = verbose\n" ;
  print "\n" ;
  print "  This is a Perl server for the cricket\n" ;
  print "  data collection program.  It waits for\n" ;
  print "  connections on port 5500, gathers\n" ;
  print "  requested data, reports it to the\n" ;
  print "  client, then waits for another connection.\n" ;
  print "\n" ;

  exit 1;

}
