#!/usr/bin/perl
#
# This script is a component of Warewulf,
# http://www.runlevelzero.net/greg/warewulf
#
#########################################################################
#
# Copyright (c) 2003, The Regents of the University of California, through
# Lawrence Berkeley National Laboratory (subject to receipt of any
# required approvals from the U.S. Dept. of Energy).  All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# The GNU GPL Document can be found at:
# http://www.gnu.org/copyleft/gpl.html
#
#########################################################################
#
# Written and maintained by:
#       Greg Kurtzer, <gmkurtzer@lbl.gov>


use lib "/usr/lib/warewulf/", "/usr/lib64/warewulf/";
use Warewulf::Config;
use IO::Socket;
use IO::Select;
#use strict;
use constant MAXLEN => 1024;
use Getopt::Long;

$SIG{PIPE} = 'IGNORE';

my $usage = "USAGE: $0 [options]
  About:
    warewulfd is the Warewulf cluster system daemon. It is responciable for
    collecting the node status, making nodes available, and handing the data
    to the user space applications when requested.

  Options:
    --debug    Do not fork into the background, and show debugging output
    --port     TCP and UDP ports to listen on (default = 9873)
    --help     Show this banner

  This tool is part of the Warewulf cluster distribution
     http://warewulf-cluster.org/
";

GetOptions(
   'debug'     => \$debug,
   'port=s'    => \$port,
   'help'      => \$help,
);

if ( $help ) {
   print $usage;
   exit;
}

unless ( $debug ) {
   if ( -f '/var/run/warewulfd.pid' ) {
      print "Warewulf daemon already running!\n";
      open(PID, "/var/run/warewulfd.pid");
      $pid = <PID>;
      close PID;
      print "PID=$pid\n";
      exit 1;
   }
   open(STDIN, "/dev/null");
   open(STDOUT, ">/dev/null");
   open(STDERR, ">/dev/null");
   fork and exit;
}   

$pid = $$;
open(PID, '> /var/run/warewulfd.pid')
   or warn "Could not create PID file at: '/var/run/warewulfd.pid'\n";
print PID $pid;
close PID;

$SIG{HUP} = sub {
   #&get_hosts;
   return('1');
};
$SIG{TERM} = sub {
   unlink('/var/run/warewulfd.pid');
   exit 1;
};
$SIG{INT} = sub {
   warn "-Cought Signal Interrupt, exiting...\n";
   unlink('/var/run/warewulfd.pid');
   exit 1;
};

if ( $port ) {
   $monitor_port = $port;
} else {
   $monitor_port = '9873';
}

my $data;

$debug and warn "Building UDP socket at '$monitor_port'\n";
my $S = IO::Socket::INET->new(Proto     => 'udp',
                              LocalPort => $monitor_port,
                              Type      => SOCK_DGRAM,
                              ) || die "can't make socket: $!";

$debug and warn "Building TCP socket at '$monitor_port'\n";
my $R = IO::Socket::INET->new(Proto     => 'tcp',
                              LocalPort => $monitor_port,
                              Listen    => 1,
                              Reuse     => 1,
                              ) || die "can't make socket: $!";

$debug and warn "Adding the socket handles to IO::Select\n";
$sel = new IO::Select();
$sel->add($S);
$sel->add($R);


my %master = &master_config;
my %nodes = &node_config;

# This simply "primes the pump" when the daemon is first started. Ither nodes
# that come up after this has been primed will automatically be added to the
# nodestats array (that is if they are on the right subnet).
$debug and warn "Priming the nodestats array\n";
nodeloop0: foreach ( keys %nodes ) {
   next nodeloop0 if ( $nodes{$_}{'skip'} );
   # Everything is down until proven otherwise
   $nodestats{"$_"}{data} = 'NODESTATUS=DOWN';
}

@nets = split(/\s*,\s*/, $master{'warewulfd'}{'udp allow'} );
foreach $net ( @nets ) {
   ($f1, $f2 ) = split(/[:\/]/, $net);
   $network = &ipcalc_network($f1, $f2);
   $udp_networks{"$network"} = 1;
   $debug and warn "allowing UDP from->$network/$f2\n";
}
@nets = split(/\s*,\s*/, $master{'warewulfd'}{'tcp allow'} );
foreach $net ( @nets ) {
   ($f1, $f2 ) = split(/[:\/]/, $net);
   $network = &ipcalc_network($f1, $f2);
   $tcp_networks{"$network"} = 1;
   $debug and warn "allowing TCP from->$network/$f2\n";
}

$debug and warn "Starting IO::Select loop\n";
$debug and warn "Listening on both UDP/TCP ports:$monitor_port\n";
while (@ready = $sel->can_read) {
   foreach $fh (@ready) {
      if ( $fh == $S ) {
         my $sender = $S->recv($data,MAXLEN,0) || die "recv(): $!";
         my ($remote_port,$remote_host) = sockaddr_in($sender);
         my $remote_ip = inet_ntoa($remote_host);
         my $network = &ipcalc_network("$remote_ip", "$master{'network'}{'admin netmask'}");
         my $remote_node = gethostbyaddr($remote_host, AF_INET);
         $remote_node =~ s/-admin//;
         if ( $udp_networks{"$network"} or ! %udp_networks ) {
            $debug and warn "Recieving=>UDP:$monitor_port -> stats from '$remote_node' ($remote_ip)\n";
            chomp($data);
            $nodestats{$remote_node}{'data'} = $data;
            $nodestats{$remote_node}{'time'} = time;
         } else {
            $debug and warn "Dropping=>UDP:$monitor_port -> connection from $network ($remote_node/$remote_ip)\n";
         }
      } elsif ( $fh == $R ) {
         my $new_sock = $R->accept();
         $addr = $new_sock->peerhost();
         my $network = &ipcalc_network("$addr", "$master{'network'}{'admin netmask'}");
         if ( $tcp_networks{"$network"} or ! %tcp_networks ) {
            $debug and warn "Sending=>TCP:$monitor_port -> stats to $addr ($network)\n";
         } else {
            $debug and warn "Dropping=>TCP:$monitor_port -> connection from $addr ($network)\n";
            close($new_sock);
            next;
         }
         $node_stats = ();
         $nodes_up = '0';
         $nodes_down = '0';
         $node_stats = "CLUSTER=$master{network}{alias}\n";
         foreach $node ( sort keys %nodestats ) {
            $node_stats .= "NODE=$node\n";
            if ( $nodestats{$node}{'time'} ) {
               $last = ( time - $nodestats{$node}{'time'} );
               if ( $last > '300' and $nodestats{$node}{'NODESTATUS'} ne 'ASLEEP' ) {
                  $nodestats{$node}{data} =~ s/NODESTATUS=.+$/NODESTATUS=DOWN/;
               }
               $node_stats .= "LASTCONTACT=$last\n";
               $node_stats .= "$nodestats{$node}{'data'}\n";
            } else {
               $node_stats .= "LASTCONTACT=never\n";
            }
         }
         print $new_sock "\n";
         print $new_sock $node_stats;
         close($new_sock);
      }
   }
}
         
exit;
