#!/usr/bin/perl -w
use strict;
#use lib qw ( @@ LIBDIR@@ );
use lib qw ( /usr/share/perl5/vendor_perl/ );
# my $kas=/"/";
use Socket;

use Carp;
use Plugins;
use Misc;
use Misc qw ( $lastrun OpenUserConfig );
use File::stat;
use POSIX qw(setsid);
my $logfile = "/var/log/lstatd";
$ENV{'PATH'} = '/bin:/usr/bin:/sbin:/usr/sbin';

#read debug level
my $Debugging = 0;
if (@ARGV == 2) {
 $Debugging = $ARGV[1];
}

if ($Debugging < 3) {
#start daemon
chdir '/' or die "Can't chdir to /: $!";
umask 0077;
#umask 0;
open STDIN, '/dev/null'   or die "Can't read /dev/null: $!";
open (STDOUT, ">$logfile") or die "Can't write to $logfile: $!";
open (STDERR, '>&STDOUT') or die "Can't redirect STDERR to STDOUT: $!";

defined (my $pid=fork ) or die "Cant' fork: $!";
exit if $pid;
setsid or die "Can't start a new session: $!";


select(STDERR); $| = 1;     # make unbuffered
select(STDOUT); $| = 1;     # make unbuffered
}

#$SIG {'CLD'} = "IGNORE";   #uncoment this line if lstad generate zombies

$lastrun = -1;


my $Forsleep = $userconfig {'LIVE_PERIOD'};
my $socketfile = $sysconfig {'SOCKET'}; #to communication with client
#my $socketfile = '/tmp/test23'; #to communication with client
my $rin;
my $netmsg;

#create socket
 umask 0111;
 my $uaddr = sockaddr_un($socketfile);
 my $proto = getprotobyname('tcp');

 socket(Server,PF_UNIX,SOCK_STREAM,0) 	|| die "cant create socket: $!";
 #chmod (0700, $socketfile);
 unlink($socketfile);
 bind  (Server, $uaddr) 			|| die "cant bind socket: $!";
 listen(Server,SOMAXCONN)			|| die "cant listen: $!";

umask 002;

my @Objects = ();
my @LiveObjects = ();
#arrays objets to update
my %UpdateByPeriod  = ();
#load all objects
LoadAllObjects();

#main program loop
my $objtable;
my $obj;
my @MyLocalTime = localtime ();  # to save local time
my $MinCounter = 0; #count minutes

while () {
 my $ti = time;
 print "Sleep: $lastrun\n" if ($Debugging);
 $lastrun++; #synchronize with update procedures

 $rin ='';
 vec ($rin, fileno (Server),1)=1;
 #wait for connection or timeout
 if (select ($rin, undef, undef,$Forsleep)) {
  accept(Client,Server);


 # while (defined ( $netmsg=<Client>)) {
  #processing command
  if ($Debugging) { printdebuginfo ("Message found") }
  my $line;
  $line = <Client>; #read command from client
   chomp ($line);
   my ($com, $objid) = split (/\s/,$line);
   print "Command: $com $objid \n" if ($Debugging);
   if ($com == 0 ) {
   #create and update new objects
   my $obj2 = SafeLoad ($objid);
   if (defined ($obj2)) {
     if ($Debugging) { printdebuginfo ("Create object") }
     $obj2 -> CreateRRD (); #create new temp objects
     if (@LiveObjects >= $userconfig {'MAX_LIVE' }){
     #too man live objects, destroy foldest object
     DestroyObj ($LiveObjects[0]->name(), \@LiveObjects);
     }
     push (@LiveObjects, $obj2);
    }
   } elsif ($com == 1) {
     #destroy object
     DestroyObj ($objid, \@LiveObjects);
   } elsif ($com == 2) {
     #reload object
     #destroy old objects from memory
     my %save_param_list;

      for (my $i=0;$i<@Objects; $i++) {
    if ($Objects[$i] -> name () eq $objid) {

    printdebuginfo ("Removing from memory $objid") if ($Debugging) ;
    $Objects[$i] -> Save_Params (\%save_param_list); #save parameters before reload
    undef $Objects[$i];
    splice (@Objects, $i, 1); #remove form  update list
    last;
                 }
          }
  #load new object
     my $obj2 = SafeLoad ($objid);
     if (defined ($obj2)) {
       $obj2 -> Restore_Params (\%save_param_list); #save parameters before reload
      push (@Objects, $obj2);
     #put again objects in period arrays
     CreateUpdateArray ();

     }
   }
   elsif ($com == 3) {
     #recreate rrd file
     RecreateRRD ($objid);
   }
  elsif ($com == 4) {
     #destroy objects
     DestroyObj ($objid, \@Objects);
     #put again objects in period arrays
     CreateUpdateArray ();
     }
  elsif ($com == 5) {
     #reload userconfig
     undef %userconfig;
     OpenUserConfig (\%userconfig);   #read user config
     $Forsleep =  $userconfig {'LIVE_PERIOD'};
     if ($Debugging) {
      printdebuginfo ("Reload userconfig");
      while ( my ($key, $value) = each %userconfig) {
       print "$key=$value\n";
      }
     }
     } #end com 5
  elsif ($com == 6) {
      #reload all objects
     printdebuginfo ("Reloading all objects")     if ($Debugging);
     LoadAllObjects();
     } #end com 6

 print Client "OK\n"; #send response to client
 close Client; #end comunication
  } #end found message



 #updating live objects
 if (@LiveObjects) {
  foreach my $obj2 (@LiveObjects) {
   if ($Debugging) {
    printdebuginfo (" Update ",$obj2->name ());
    }
   $obj2 -> UpdateRRD ();
  }
 }

 if (defined ($UpdateByPeriod {'SEC'})) {
  #update second
  $objtable = $UpdateByPeriod {'SEC'};
  foreach my $key (@$objtable) {
   if (($ti - $$key[2]) >= $$key[1]) {
   #need update
   $obj = $$key[0];
   if ($Debugging) {
    printdebuginfo (" Update ",$obj->name ());
    }
   #update objects data
   $obj -> UpdateRRD ();
   $$key[2] = $ti; #save last update time
   }
  }
 }


 my @ltime = localtime ($ti); #convert to local time
 if ($ltime [1] != $MyLocalTime [1]) {
 $MyLocalTime [1] = $ltime [1];
  #whole minute
  #update obj by MIN, HOUR ...
  UptadeSecondOBJ ('MIN');

  if ($MinCounter++ > 5 ) { #test every 5 min for forgotten live objects
    $MinCounter = 0;
    #try destroy lost live objects
    if (@LiveObjects) {
     #read png filelist
   my $PNGDir=$sysconfig{'STAT_PNG_DIR'};
   if (opendir (DIR, "$PNGDir")) {
   my @files = grep {/\.png$/} readdir DIR;
   closedir DIR;
     map {TestLive ($_,\@files) } @LiveObjects;
    }
   }
  }

  if ($ltime [2] != $MyLocalTime [2]) {
  $MyLocalTime [2] = $ltime [2];
   #whole hour
   UptadeSecondOBJ ('HOUR');

 
   if ($ltime [3] != $MyLocalTime [3]) {
   $MyLocalTime [3] = $ltime [3];

    #new day
    UptadeSecondOBJ ('DAY');

    if ($ltime [4] != $MyLocalTime [4]) {
    $MyLocalTime [4] = $ltime [4];
     #new month
     UptadeSecondOBJ ('MONTH');
     }
     if (!$ltime [6]) {
     #new week - Sunday
     UptadeSecondOBJ ('WEEK');
     }
    }
   }
 }
}


#put objects in period arrays
sub CreateUpdateArray {

#free all objects
if (%UpdateByPeriod ) {
 foreach my $key (keys %UpdateByPeriod) {
  delete $UpdateByPeriod {$key};
 }
}

#tempolary for collecting
my @UpdateBySec = ();
my @UpdateByMin = ();
my @UpdateByHour = ();
my @UpdateByDay = ();
my @UpdateByWeek = ();
my @UpdateByMonth = ();

foreach my $obj (@Objects) {
#setting debug information
$obj -> debug ($Debugging);
#try create rrd file if not exists
if ( ! -e $obj ->{'RRD_FILENAME'} ) {
 printdebuginfo ("Create RRD file for ", $obj->name) if ($Debugging);
 $obj -> CreateRRD;
}

#grouping object to arrays by period
my $up = $obj-> UpdatePeriod ();
 if ($up =~ /^\d+$/) {
 #only seconds
 push (@UpdateBySec, [$obj, $up,0]); #object referenct, update period and last updated
 }else {
 #rest (minutes, hour, day... )
 $_ = $up;
 (my $value, my $per) = /(\d+)(\w+)/;

 push (@UpdateByMin, [$obj, $value,0]) if ($per eq 'm' );
 push (@UpdateByHour, [$obj, $value, 0]) if ($per eq 'h' );
 push (@UpdateByDay, [$obj, $value, 0]) if ($per eq 'd' );
 push (@UpdateByWeek, [$obj, $value, 0]) if ($per eq 'w' );
 push (@UpdateByMonth, [$obj, $value, 0]) if ($per eq 'M' );
 }
}
$UpdateByPeriod {'SEC'} = [ @UpdateBySec ] if ($#UpdateBySec >= 0);
$UpdateByPeriod {'MIN'} = [ @UpdateByMin ] if ($#UpdateByMin >=0 );
$UpdateByPeriod {'HOUR'} = [ @UpdateByHour ] if ($#UpdateByHour >= 0);
$UpdateByPeriod {'DAY'} = [ @UpdateByDay ] if ($#UpdateByDay >= 0);
$UpdateByPeriod {'WEEK'} = [ @UpdateByWeek ] if ($#UpdateByWeek >= 0);
$UpdateByPeriod {'MONTH'} = [ @UpdateByMonth ] if ($#UpdateByMonth >= 0);
if ($Debugging) { printObjectsList () };
}

sub printObjectsList {
 foreach my $key ( keys %UpdateByPeriod ) {
 my $tableref = $UpdateByPeriod {$key};
 print "-------$key--------\n";
 foreach my $key2 (@$tableref ) {
  my $obj = $$key2[0];
  print ($obj->name (),"  ",$obj->UpdatePeriod ()," \n");
 }
 }
}

#update obj by MIN, HOUR ...
sub UptadeSecondOBJ {
my $type =shift;
my $objtable;
my $obj;

  if ($Debugging > 1 ) {printdebuginfo ("Whole $type") }
  if (defined ($UpdateByPeriod {$type})) {
   #search for update
   $objtable = $UpdateByPeriod {$type};
   foreach my $key (@$objtable) {
   if (!$$key[2]) {
   #need update
   $obj = $$key[0];
   if ($Debugging) {
    printdebuginfo ("Update ",$obj->name ());
    }
   #update objects data
   $obj -> UpdateRRD ();
   $$key[2] = $$key[1]; #save last update time
   $$key[2] --;
    }
     else {$$key[2] -- }
   }
  }
}

sub printdebuginfo {
 my $t = localtime ();
 print $t," ";
 foreach my $info (@_){
 print $info;
 }
 print "\n";
}

#destroy object
sub  DestroyObj {
 my $objid = shift;
 my $OBJTab=shift;
 my $found=0;

 if ($Debugging) {
    printdebuginfo ("Attempt to destroing object $objid");
    }

 for (my $i=0;$i<@$OBJTab; $i++) {
  if ($$OBJTab[$i] -> name () eq $objid) {

    printdebuginfo ("Destroing object $objid") if ($Debugging) ;

    $found =1;
 #destroing
   my $objfile = $sysconfig {'OBJ_DIR' }.$objid;
   unlink ($objfile.'.obj'); #delete objects file
   unlink ($sysconfig {'RRD_DIR' }.$objid.'.rrd'); #delete his rrd file
   splice (@$OBJTab, $i, 1); #remove form  update list
   last;
  }
 }
 printdebuginfo ("Destroing object not found.") if ($Debugging && (! $found));

}

#testing if live object is osbserved yet
sub TestLive {
 my $obj = shift;
 my $files = shift;
 my $Result = 0;
 my $PNGDir=$sysconfig{'STAT_PNG_DIR'};
 my $myname = $obj->name ();
 foreach my $filename (@$files) {
 #test if our png file
 if ($filename =~ /\_$myname\.png/) {
 printdebuginfo ("Testing PNG file $filename")    if ($Debugging);
 $filename = $PNGDir.$filename;
 if (-e $filename ) {
  my $mtime = stat ($filename) ->mtime;
  my $diff = time -$mtime;
  printdebuginfo ("Difference $diff second")    if ($Debugging);
  if ($diff < 60) { # 1 min
  $Result=1
  } else {
   unlink ($filename); #forget this png
  }
  }
 }
 }
 if (!$Result) { #Destroy
   DestroyObj ($obj -> name (), \@LiveObjects);
  }
 return ($Result);
}

sub RecreateRRD {
 my $ObjName = shift;
 printdebuginfo ("Prepare recreate RRDfile for $ObjName")    if ($Debugging);
 for (my $i=0; $#Objects; $i++) {
  my $obj = $Objects[$i];
  if ($obj->name () eq $ObjName ){
    printdebuginfo ("Recreate RRDfile for $ObjName")    if ($Debugging);
    $obj = SafeLoad ($ObjName); # load new object to memory
    if (! defined ($obj)) { croak "Can't load object: $ObjName \n" };
    $obj -> debug ($Debugging);
    $obj -> CreateRRD; #create new rrd
    $Objects[$i] = $obj;

    #put again objects in period arrays
   CreateUpdateArray ();

    last;
  }
 }
}

#load object with security test
sub SafeLoad {
 my $objname =shift;
 my $obj = LoadObjectByName ($objname); # load object to memory
 if (defined ($obj)) {
  #check security
  if ( my $err_msg = $obj -> Check_Security ()) {
  my $msg="Insecure object: ".$obj ->name().$err_msg;
  croak ($msg);
  }
 } else
  {
   croak "Unknown object for load: $objname";
  }
 return ($obj)
}

#load all objects to memory and create rrd files
sub LoadAllObjects {

@Objects = ();
@LiveObjects = ();
#arrays objets to update
%UpdateByPeriod  = ();
LoadObjects (\@Objects); #load all objects in memory
#check security of all objects
foreach my $obj (@Objects) {
#setting debug information
 if ( my $err_msg = $obj -> Check_Security ()) {
  my $msg="Insecure object: ".$obj ->name().$err_msg;
  croak ($msg);
  }
}

printdebuginfo ("Objects loaded") if ($Debugging);

#put objects in period arrays
CreateUpdateArray ();

}

