#!/usr/bin/env perl
use strict;
use warnings;
use diagnostics;

#
# $Id: rrdtimer.pl,v 1.3 2003/10/16 10:25:36 bernisys Exp $
#
# use Getopt::Long;
use FindBin;
use lib "./lib";
use lib $FindBin::RealBin;
use HotSaNICparser;
use subs qw(main_loop call_modules call_module_read daemonize);

# let all outputs be flushed directly
$|=1;

# set commandline correctly
$0="rrdtimer";

(my $CALLDIR=$FindBin::RealBin) =~ s/\/rrdtimer//g;
(my $VERSION = '$Revision: 1.3 $') =~ s/.*(\d+\.\d+).*/$1/;
(my $IDENTIFIER = '$Id: rrdtimer.pl,v 1.3 2003/10/16 10:25:36 bernisys Exp $') =~ s/.*,v (.*) \$/$1/;

print "reading & checking config ($CALLDIR/settings) ...\n";
my %CONFIG=HotSaNICparser::get_config($CALLDIR);
$CONFIG{MODULEDIR}=$CONFIG{DAEMONDIR}."/modules";
$CONFIG{SCHEDULED}=1;

print "evaluating cmdline arguments...\n";
my ($debuglevel,$runmode)=check_for_args(@ARGV);
if ($debuglevel>$CONFIG{"DEBUGLEVEL"}) { $CONFIG{"DEBUGLEVEL"}=$debuglevel; }


if ($CONFIG{DEBUGLEVEL} >0) { foreach my $k (keys %CONFIG) { print "$k=$CONFIG{$k}\n"; }}
if ($runmode < 128) { exit 0; }

my $PID=HotSaNICparser::get_pid($CONFIG{DEBUGLEVEL},$CONFIG{PIDFILE},"rrdtimer");
if ( $PID>0 ) {
  print "\nAnother process is running on PID $PID - daemon stopped.\n\n";
  exit 1;
  }

my $debug="";
if (($runmode&1)>0) { print "Debug mode enabled!\nIdentifier: $IDENTIFIER,\nLogdir: ",$CONFIG{LOGDIR},"\nPIDfile: ",$CONFIG{PIDFILE},"\n\n"; $debug="d"; }

my @PIDs;
my %PIDs;

daemonize();

$SIG{TERM} = \&signalhandler;
$SIG{HUP} = \&signalhandler;
$SIG{USR1} = \&signalhandler;
$SIG{USR2} = \&signalhandler;

main_loop();


######################################################################
#                                                                    #
#  SUBROUTINES BEGIN HERE                                            #
#                                                                    #
######################################################################


######################################################################
#
# signal-handler
#  TERM -> terminate daemon and modules
#  HUP  -> re-configure
#  USR1 -> kill daemon only
#  USR2 -> kill modules only
#
sub signalhandler {
  my ($sig)=@_;

  if (defined $sig) {
    if (($sig eq "TERM") ||($sig eq "USR2")) {
      print time,"Stopping all running modules.\n";
      if (@PIDs) { kill "TERM", @PIDs; @PIDs=(); }
      }

    if (($sig eq "TERM") || ($sig eq "USR1")) {
      print time,"Daemon exiting normally.\n";
      if (-e $CONFIG{PIDFILE}) {
        unlink $CONFIG{PIDFILE};
	print "PID-file removed.\n";
        }
      close STDIN;
      close STDOUT;
      close STDERR;
      exit 0;
      }

    if ($sig eq "HUP") {
      print time," HUP signal received - signal not implemented yet...\n";
      foreach (keys %CONFIG) { print $_," -> ",$CONFIG{$_},"\n"; }
      # TODO:
      #
      #  - kill unused modules
      #  - start configured modules (if necessary)
      #  - re-configure all modules
      #
      }
    }
  }


######################################################################
#
# the main loop executes all timed scripts:
#
# modules/*/read-data every >=10 sec. (hardcoded)
# modules/*/diagrams every $DTIME
# convert() every $CTIME if $CONVERTMETHOD is other than "HTML"
# scan_for_modules() every $STIME
#
sub main_loop {
  my ($now,$last,$sleeptime,$lastscan,$lastdiagram,$lastconvert,@modules);

  $now=time;
  $last=$now-100;
  $lastscan=$now;
  $lastdiagram=int($now/$CONFIG{DTIME})*$CONFIG{DTIME};
  $lastconvert=int($now/$CONFIG{CTIME})*$CONFIG{CTIME};

  @modules=HotSaNICparser::scan_for_modules($CONFIG{MODULEDIR},$CONFIG{DEBUGLEVEL},$CONFIG{RUN});
  if ($CONFIG{DEBUGLEVEL} > 0) { print time,": initializing modules...\n"; }
  initialize_modules(@modules);
  %PIDs=call_modules(@modules);
  @PIDs=keys(%PIDs);


  while () {
    $now=time;
#   if ($last+10 <= $now) {
      if ($CONFIG{DEBUGLEVEL} > 0) { print "$now: main loop running\n"; }
  
      # scan for modules and (re-)start them if necessary
      #
      if ($lastscan+$CONFIG{STIME} <= $now) {
        @modules=HotSaNICparser::scan_for_modules($CONFIG{MODULEDIR},$CONFIG{DEBUGLEVEL},$CONFIG{RUN});
        %PIDs=call_modules(@modules);
        @PIDs=keys(%PIDs);
        $lastscan=$now;
        if ($CONFIG{DEBUGLEVEL} < 0) { logrotate(); }
        }
  
      # for each module call the read-data script.
      #
      if ($CONFIG{DEBUGLEVEL} > 0) { print "$now: signaling ",join " ",@PIDs,"\n"; }
      if (@PIDs) {
	if ($CONFIG{SCHEDULED} == 0) { kill "SIGUSR1", @PIDs; }
	else {
          foreach (@PIDs) {
            kill "SIGUSR1", $_;
            my $wait=int($CONFIG{SCHEDULE_MIN}+rand($CONFIG{SCHEDULE_MAX}-$CONFIG{SCHEDULE_MIN}))/1000;
            if ($CONFIG{DEBUGLEVEL} >3) { print "$wait($PIDs{$_}) "; }
            select(undef,undef,undef,$wait);
	    }
          if ($CONFIG{DEBUGLEVEL} >3) { print "\n"; }
          }
        }
      $last=$now;
#     }
  
    # check if diagrams have to be built.
    #
    if ($lastdiagram+$CONFIG{DTIME} <= $now) {
      my $append;
      if ($CONFIG{DIAGRAMLOG} eq "all") { $append=1; }
      call_script($CONFIG{DAEMONDIR},"diagrams.pl",5,$CONFIG{LOGDIR}."/diagram.log",$append);
      $lastdiagram+=$CONFIG{DTIME};
  
      # check if mainpage-diagrams have to be generated.
      #
      if (($lastconvert+$CONFIG{CTIME} <= $now) && ($CONFIG{CONVERTMETHOD} ne "HTML")) { 
      call_script($CONFIG{DAEMONDIR},"convert.pl",0);
        $lastconvert+=$CONFIG{CTIME};
        }
      }
    $sleeptime=10-(time % 10);
    sleep $sleeptime;
    }
  }


######################################################################
#
# call init script in each module (if available)
#
sub initialize_modules {
  my (@modules)=@_;
  my ($name,$ouser,$osystem,$user,$system,$utime,$stime);

  print time," Initializing modules...\n";
  for $name (@modules) {
    unlink $CONFIG{MODULEDIR}."/$name/*.dat";
#   unlink $CONFIG{MODULEDIR}."/$name/*.pid";
    unlink $CONFIG{MODULEDIR}."/$name/*.old";
    if ( -e $CONFIG{MODULEDIR}."/$name/init" ) {
      print time," initializing ",$name,"\n";
      system "cd ".$CONFIG{MODULEDIR}."/$name; ./init";
      print time," initializing of ",$name," DONE!\n\n";
      }
    }
  print time," END Initializing modules...\n","-" x 50,"\n\n";
  }


######################################################################
#
# start read-data.pl script in each module
#
sub call_modules {
  my (@modules)=@_;
  my %PIDs;
  my $nicelevel="0";

  # iterate through all configured modules...
  #
  for my $module (@modules) {
    chdir $CONFIG{MODULEDIR}."/$module";

    my $PID=HotSaNICparser::get_pid($CONFIG{DEBUGLEVEL});

    # if not running, start the module and wait for PIDfile to be generated
    #
    if ($PID == 0) {
      my $count=-1;
      print "\nstarting module: \"$module\"\n"; 
      if (-e "./running.pid") {
        if ($CONFIG{DEBUGLEVEL}>1) { print "removing old PIDfile...\n"; }
        unlink "./running.pid";
        }
      if (-e "./read-data-wrapper.pl") { system("nice -$nicelevel ./read-data-wrapper.pl start $module"); }
      else { system("nice -$nicelevel ./read-data.pl start $module"); }
      $count=0;
      while ( ($count<20) && (! -s "./running.pid") ) {
        $count++;
        print ".";
        sleep 1;
        }
      $PID=HotSaNICparser::get_pid($CONFIG{DEBUGLEVEL});
      }

    if ($PID >0) {
      print "$module running on PID $PID\n\n";
      $PIDs{$PID}=$module;
      }
    }
  return %PIDs;
  }


######################################################################
#
# call external script (incl. sanity checking etc)
#
sub call_script {
  my ($path,$script,$nicelevel,$output,$append)=@_;

  if (! defined $append) { $append=0; }
  if ( index("1 yes",lc $append) < 0) { rename $output,"$output.old"; }
  if (! defined $output) { $output=""; }
  if (! defined $nicelevel) { $nicelevel=0; }

  if ( (defined $path) && (defined $script) ) {
    print time,": executing $path/$script with nice $nicelevel\n";
    if ( -e "$path/$script") {
      if ($output ne "") {
        if ($nicelevel ne 0) {system ("cd \"$path\"; nice -$nicelevel ./$script >$output &"); }
        else {system ("cd \"$path\"; ./$script >$output &"); }
        }
      else {
        if ($nicelevel ne 0) {system ("cd \"$path\"; nice -$nicelevel ./$script &"); }
        else {system ("cd \"$path\"; ./$script &"); }
        }
      }
    else { print time.": script not found! Check your installation.\n"; }
    }
  }


######################################################################
#
# fork into background
#
sub daemonize {
  print "entering daemon mode...\n";
  if ($debug eq "") {
    close STDIN;
    close STDOUT;
    close STDERR;
    if (open(DEVTTY, "/dev/tty")) { ioctl(DEVTTY,0x20007471,0); close DEVTTY; }
    open STDIN,"/dev/null";
    open STDOUT,">>".$CONFIG{LOGDIR}."/HotSaNIC.log";
    open STDERR,">&STDOUT";
    chdir "/";
    fork && exit 0;
    print "\n\n----------------------------------------\n";
    print time,": archiver successfully forked into background and running on PID $$\n";
    }
  else { print time,": archiver running in debug-mode on PID $$\n"; }
  open FILE,">".$CONFIG{PIDFILE};
  print FILE $$;
  close FILE;
  }


######################################################################
#
# rotate logfiles
#
sub logrotate {
  my ($size,$file);

  if ((defined $CONFIG{DEBUGLEVEL}) && ($CONFIG{DEBUGLEVEL} < 0)) {

    $file=$CONFIG{LOGDIR}."/HotSaNIC.log";
    (undef,undef,undef,undef,undef,undef,undef,$size,undef,undef,undef,undef,undef) = stat($file);
    if (defined $size) {
      if ($size > $CONFIG{LOGSIZE}) {
	print time," $file exceeding ",$CONFIG{LOGSIZE}," bytes - rotating - keeping ",$CONFIG{LOGBACKUPS}," backups.\n";
        for (my $nn=$CONFIG{LOGBACKUPS};$nn>1;$nn--) {
	  if ( -e "$file.$nn" ) { unlink "$file.$nn"; }
	  if ( -e "$file.".($nn-1) ) { rename "$file.".($nn-1),"$file.".$nn; }
          }
        close STDOUT;
        rename $file,"$file.1";
        open STDOUT,">>$file";
        close STDERR;
        rename $file,"$file.1";
        open STDERR,">>$file";
        }
      }
    }
  }


######################################################################
#
# evaluate commandline arguments
#
sub check_for_args {
  my @args=@_;

  # set initial values
  #
  my $debuglevel=-1;
  my $runmode=0;

  # Check if we were called with any options
  #
  foreach my $arg (@args) {
    if (index($arg,"d") >= 0) { $runmode|=1; }
    if (index($arg,"D") >= 0) { $runmode|=128; }
    if (index($arg,"?") >= 0) { $runmode|=256; }
    $arg=~ s/[a-zA-Z]//g;
    if ($arg =~ /[0-9]/) { if ($arg > 0) { $debuglevel=$arg; } }
    }
  
  if (($runmode == 0) || ($runmode > 255)) {
    print "rrdtimer - CVS version $VERSION ($IDENTIFIER) - OS: $^O\n";
    print "usage:\nrrdtimer [options[debuglevel]]\n  options:\n";
    print "  D - enter daemon-mode (i.e. start the main loop)\n";
    print "  d - enter debugging mode (don't fork into background)\n";
    print "\n";
    exit 0;
    }
  return ($debuglevel,$runmode);
  }


