#!/usr/bin/perl
# IBM_PROLOG_BEGIN_TAG 
# This is an automatically generated prolog. 
#  
#  
#  
# Licensed Materials - Property of IBM 
#  
# (C) COPYRIGHT International Business Machines Corp. 2005,2007 
# All Rights Reserved 
#  
# US Government Users Restricted Rights - Use, duplication or 
# disclosure restricted by GSA ADP Schedule Contract with IBM Corp. 
#  
# IBM_PROLOG_END_TAG 
# @(#)65   1.19   src/csm/install/nodestatusd.perl, setup, csm_rfish, rfishs001b 1/22/06 21:38:19
#####################################################################

package Server;
use strict;
use IO::Socket;
use IO::File;
use POSIX qw(WNOHANG setsid);
use Sys::Syslog qw(:DEFAULT setlogsock);
use Getopt::Std;
use Fcntl qw(:DEFAULT :flock);

sub new {
	my ($pkg,$dir)=@_;
	bless {
		_dir => "$dir" ,
		_hostsfile => "$dir/csm/status/nodemsg_requests",
		allow_file => "/var/opt/csm/AllowNodeMsgs",
		allow_nodes => undef
	}, $pkg;
}

sub daemonic {
	my $child = fork();

	if(!defined $child) {
		die MessageUtils->messageFromCat('csmInstall.cat', $::MSGMAPPATH,
                                         'csminstall',     'E1', 
                                         'EMsgCannotFork'
                                         );
	}
	if($child) {
		exit 0;
	}
	setsid();
	open(STDIN, "</dev/null");
	open(STDOUT,">/dev/null");
	open(STDERR,">&STDOUT");
	chdir('/');
	umask(0);
	return $$;
}

sub lockfile {
	my $pkg = shift;
	my $file = shift;
	my $fh;

	if(-e $file) {
		if(!($fh = IO::File->new($file))) {
			return($fh);
		}
		my $pid = <$fh>;
		if(kill 0 => $pid) {
			die 
            MessageUtils->messageFromCat('csmInstall.cat', $::MSGMAPPATH,
                                         'csminstall',     'E1', 
                                         'EMsgSERVER_ALREADY_RUNNING', $pid
                                         );
		}
		warn "Removing PID file for defunct server process $pid.\n";
		unless(-w $file && unlink $file) {
			die
            MessageUtils->messageFromCat('csmInstall.cat', $::MSGMAPPATH,
                                         'csminstall',     'E1', 
                                         'EMsgCANT_UNLINK_PID_FILE', $pid
                                         ); 
		}
	}
	if($fh = IO::File->new($file,O_WRONLY|O_CREAT|O_EXCL,0644)) {
		return($fh);
	}
	else {
		die 
        MessageUtils->messageFromCat('csmInstall.cat', $::MSGMAPPATH,
                                     'csminstall',     'E1', 
                                     'EMsgCANT_CREATE', $file
                                     );
	}
}

sub check_allow
{
	my $pkg=shift;
	my $node=shift;
	my $allow_file=$pkg->{allow_file};
	my $ret;
	
	my @mgmtsvr=split(',',$::mgmtsvr);
	if(grep(/^$node$/,@mgmtsvr))
	{ return 1; }
	open(ALLOW, "<$allow_file") or MessageUtils->message('E2','EMsgCANT_OPEN_FILE',$allow_file);
	flock(ALLOW, LOCK_SH) or MessageUtils->message('E2','EMsgCANT_LOCK_FILE',$allow_file);
	my @allow_nodes=<ALLOW>;
	if(grep(/$node/,@allow_nodes))
	{ $ret=1;}
	else { $ret=0;}
	close(ALLOW);
	return $ret;
}

#Remove node entry in NodeAllowMsgs file
#If there's no entry left,set return value to zero to let the daemon exit

sub update_allow_file
{
        my $pkg=shift;
        my $node=shift;
        my @allow_nodes;
        my $allow_file=$pkg->{allow_file};
        my $newfile="$allow_file.new";
        my $left=0;

        open(ALLOW, "+<$allow_file") or MessageUtils->message('E2','EMsgCANT_OPEN_FILE',$allow_file);
        open(NEW, ">$newfile") or MessageUtils->message('E2','EMsgCANT_OPEN_FILE',$newfile);
        flock(ALLOW, LOCK_EX) or MessageUtils->message('E2','EMsgCANT_LOCK_FILE',$allow_file);
        @allow_nodes=<ALLOW>;
        foreach my $line (@allow_nodes)
        {
                my @ipList=split(' ',$line);
                if(grep(/^$node$/,@ipList)){ next;}
                print NEW $line;
                $left=1;
        }
        rename $newfile,$allow_file;
        if($left){
                close(ALLOW);
                return 1;
        }
        else {
                close(ALLOW);
                return 0;
        }
}
#Process CSM_stop_logging message
#If this is the last node,kill it's parent process and the process which process status file. 
sub process_stop_logging
{
	my $pkg=shift;
	my $msg_node=shift;
	my $nodename=shift;
	my $line=shift;
	my $status=shift;
	my $cmd=$pkg->getcmd;
	my @mgmtsvr=split(',',$::mgmtsvr);
	
	if(grep(/^$msg_node$/,@mgmtsvr))
	{
		#If one node's installation timeout,atd on management server will send stop logging message to nodestatusd
		#with the node's ip address in the status variable 
		$msg_node=$status;
	}	
	if(	!( $pkg->update_allow_file($msg_node) ) )
	{ 
		sleep 10;
		if(!$::restart)
		{
			
			my $parent=getppid;
			kill 'TERM', $::process_status;
			kill 'TERM', $parent;
			return 1;
		}	
	}
	return 0;

}
#Interact with client's request
#Dispatch the request if it's an authorized node.
sub interact {
	my $pkg = shift;
	my $sock = shift;
	#ip address of node who send this message
	my $msg_node = shift;
	my $ready = 0;
	my $ret=0;
	my $cmd=$pkg->getcmd;	
	my @output;

	STDIN->fdopen($sock,"<")  or die 
    MessageUtils->messageFromCat('csmInstall.cat',       $::MSGMAPPATH,
                                 'csminstall',           'E1', 
                                 'EMsgCANT_REOPEN_FILE', 'STDIN', 
                                  $!
                                 );
	STDOUT->fdopen($sock,">") or die 
    MessageUtils->messageFromCat('csmInstall.cat',       $::MSGMAPPATH,
                                 'csminstall',           'E1', 
                                 'EMsgCANT_REOPEN_FILE', 'STDOUT', 
                                  $!
                                 );
	STDERR->fdopen($sock,">") or die 
    MessageUtils->messageFromCat('csmInstall.cat',       $::MSGMAPPATH,
                                 'csminstall',           'E1', 
                                 'EMsgCANT_REOPEN_FILE', 'STDERR', 
                                  $!
                                 );
	$| = 1;

	while(<>) {
		chomp;
		my @attrs = split(/\s/,$_);
		my $nodename=shift @attrs;
		my $line=shift @attrs;
		my $status=join " ",@attrs;
		if($line =~ /^CSM_start_session\b/ )
		{
			if( !$pkg->check_allow($msg_node) )
			{
				MessageUtils->message('W','IMsgUnauthNode',$msg_node);
				last;
			}
			else
			{
				print "ready\n";
				$ready=1;
			}
		}
		elsif($line =~ /^CSM_status_message\b/)
		{
			if($ready)
			{
			print "done\n";
			#If this is MS,write to CSM status file
			#Otherwise write the request to the diskboot_hosts
			if($::debug)
			{
				MessageUtils->message('L','IMsgProcessNodeMsg',$msg_node,$nodename,$line,$status);
			}
			$pkg->process_status_msg($nodename,$line,$status);
			}
			#last;
		}	
		elsif($line =~ /^CSM_disk_boot\b/)
		{
			if($ready)
			{
			print "done\n";
			MessageUtils->message('L','IMsgProcessNodeMsg',$msg_node,$nodename,$line,$status);
			$pkg->process_disk_boot($nodename,$line,$status);
			#check config_info file and installmethod
			#check pxe hex file
			#record to diskboot_hosts file
			}
			#last;
		}
		elsif($line =~ /^CSM_MinManaged_complete\b/)
		{
			if($ready)
			{
				print "done\n";
				MessageUtils->message('L','IMsgProcessNodeMsg',$msg_node,$nodename,$line,$status);
				$pkg->process_min_comp($nodename,$line,$status);
			}
			#last;
		}
		elsif($line =~ /^CSM_stop_logging\b/)
		{
			if($ready)
			{
				MessageUtils->message('L','IMsgProcessNodeMsg',$msg_node,$nodename,$line,$status);
				$ret=$pkg->process_stop_logging($msg_node,$nodename,$line,$status);
				print "done\n";
			}
			#last;
		}
		elsif($line =~ /^CSM_hwmaint_status_message\b/)
		{
			if($ready)
			{
			print "done\n";
			#If this is MS,write to CSM status file
			#Otherwise write the request to the diskboot_hosts
			MessageUtils->message('L','IMsgProcessNodeMsg',$msg_node,$nodename,$line,$status);
			$pkg->process_hwmaint_msg($nodename,$line,$status);
			}
			#last;
		}			
		else
		{
			#invalid message
			MessageUtils->message('W','IMsgInvalidNodeMsg',$msg_node,$_);
			print "go away!\n";
			last;
		}
	}

	MessageUtils->message('L','IMsgConnectionEnd',$msg_node);
	return $ret;
}
sub write_status
{
	my ($pkg,$nodename,$status) = @_;
	my $dir=$pkg->{_dir};
	
	my $statusFile = "$dir/csm/status/$nodename";

	open(STATUS_FILE, ">>$statusFile")  or MessageUtils->message('E2','EMsgCANT_WRITE_FILE',$statusFile);
	flock (STATUS_FILE, LOCK_EX) or MessageUtils->message('E2','EMsgCANT_LOCK_FILE',$statusFile);

	my $date = `date`;
	chomp $date;
	my ($flag,$msg)=split(':',$status);
	if ("$flag")
	{
		print STATUS_FILE $date . ": $msg: status=$flag\n";
	}
	else
	{
		print STATUS_FILE $date . ": $msg\n";
	}
	close(STATUS_FILE);

}
sub update_dir
{
	return 0;
}
1;

package MS;
use strict;
use IO::Socket;
use IO::File;
use POSIX qw(WNOHANG setsid);
use Sys::Syslog qw(:DEFAULT setlogsock);
use Getopt::Std;
use Fcntl qw(:DEFAULT :flock);
our @ISA = qw (Server);

#process hosts file
#change the boot method of the node in hosts file to disk
sub process_hosts
{
	my $pkg=shift;
	sleep 5;
	my $hostsFile=$pkg->{_hostsfile};
	my $dir=$pkg->{_dir};
	my ($hostname,@hostlist);
	
	if(! -e $hostsFile){
		return 0;
	}

	open (HOSTS_FILE, "+< $hostsFile") or MessageUtils->message('E2','EMsgCANT_READ_FILE',$hostsFile);
	flock (HOSTS_FILE, LOCK_EX) or MessageUtils->message('E2','EMsgCANT_LOCK_FILE',$hostsFile);
	while($hostname = <HOSTS_FILE>){
		chomp $hostname;
		push @hostlist, $hostname ;
	}
	truncate(HOSTS_FILE, 0) or MessageUtils->message('E2','EMsgCANT_TRUNC_FILE',$hostsFile);
	close(HOSTS_FILE);

	if(scalar @hostlist == 0){
		return 0;
	}
	MessageUtils->message('I','IMsgProcessRequest');

	NodesetUtils->setupBoot("disk", "onlyhex", @hostlist);
	foreach my $nodename (@hostlist)
	{
		NodeUtils->runcmd("touch $dir/csm/status/$nodename.rebootready",-1);
	}
	
}
sub getcmd
{
	my $cmd="/opt/csm/csmbin/nodestatus";
	return $cmd;
}	
sub process_status_msg 
{
	my $pkg=shift;
	my $nodename=shift;
	my $line=shift;
	my $status=shift;
	my $cmd=$pkg->getcmd;

	$pkg->write_status($nodename,$status);
	
}
sub process_hwmaint_msg 
{
	my $pkg=shift;
	my $nodename=shift;
	my $line=shift;
	my $status=shift;
	my $cmd=$pkg->getcmd;
 	
	# Process hw maintenance messages from the pxe-booted node
	#
	# If we get the BEGIN message, open the local status file,
	# truncating any existing contents.  The global file will have
	# already been created by the hwmaint command.  Otherwise,
	# open both for append and write the message.
	#
	my $local_file = "/csminstall/csm/fw/status/$nodename";
	my $global_file = "/csminstall/csm/fw/status/global";
	
	if($line =~ /CSM_HWMAINT_ACTION_BEGIN/)
	{
        unless(open LOCALFILE, ">$local_file")
        {
            MessageUtils->message('E1', 'EMsg_CANT_OPEN', $local_file);
        }
        close LOCALFILE;
	}
	else
	{
        unless(open LOCALFILE, ">>$local_file")
        {
            MessageUtils->message('E1', 'EMsg_CANT_OPEN', $local_file);
        }
        
        # Attempt to open and lock the lock for the global status file.  This
        # ensures that only one nodestatusd process at a time can modify the
        # global status file.
        open STATUSLOCK, "+<$::HWMAINT_STATUSLOCK";
        flock STATUSLOCK, $::LOCK_EX;
        
        unless(open GLOBALFILE, ">>$global_file")
        {
            MessageUtils->message('E1', 'EMsg_CANT_OPEN', $global_file);
        }
        
        # remove the CSM keyword from the string
        #
        $status =~ s/CSM_hwmaint_status_message//;
        
        print GLOBALFILE "$nodename: $status\n";
        close GLOBALFILE;
        
        # now release the status file lock
        #
        flock STATUSLOCK, $::LOCK_UN;
        
        print LOCALFILE "$nodename: $status\n";
        close LOCALFILE;
	}
}

sub process_disk_boot
{
	my $pkg=shift;
	my $nodename=shift;
	my $hostsFile=$pkg->{_hostsfile};

	open (HOSTS_FILE, ">>$hostsFile") or MessageUtils->message('E2','EMsgCANT_WRITE_FILE',$hostsFile);
	flock (HOSTS_FILE, LOCK_EX) or MessageUtils->message('E2','EMsgCANT_LOCK_FILE',$hostsFile);
	print HOSTS_FILE "$nodename\n";
	close(HOSTS_FILE);

}
sub process_min_comp
{
	my $pkg=shift;
	my $nodename=shift;
	my $line=shift;
	my $status=shift;
	my $cmd=$pkg->getcmd;
	NodeUtils->runcmd("$cmd 3 $nodename",-1); 	
}
1;

package IS;
use strict;
use IO::Socket;
use IO::File;
use POSIX qw(WNOHANG setsid);
use Sys::Syslog qw(:DEFAULT setlogsock);
use Getopt::Std;
use Fcntl qw(:DEFAULT :flock);

our @ISA = qw (Server);
#sub new { $pkg = shift; $pkg->SUPER::new(); }

sub process_hosts
{
	my $pkg=shift;
	my $ms=shift;
	my $port=shift;
	my $dir=$pkg->{_dir};
	my $hostsFile=$pkg->{_hostsfile};
	my $cmd=$pkg->getcmd;
	sleep 5;
	if(-e $hostsFile)
	{
		open(HOSTS_FILE, "+< $hostsFile") or MessageUtils->message('E2','EMsgCANT_READ_FILE',$hostsFile);
		flock(HOSTS_FILE, LOCK_EX) or MessageUtils->message('E2', 'EMsgCANT_LOCK_FILE',$hostsFile);

		my @lines = <HOSTS_FILE>;
		truncate(HOSTS_FILE, 0) or MessageUtils->message('E2','EMsgCANT_TRUNC_FILE',$hostsFile);
		close(HOSTS_FILE);
		if(@lines)
		{
			MessageUtils->message('I','IMsgProcessRequest');
			my $child = fork();
			if(!defined $child) {
		        die MessageUtils->messageFromCat('csmInstall.cat', $::MSGMAPPATH,
                                                 'csminstall',     'E1', 
                                                 'EMsgCannotFork'
                                                 );
			}
			if ($child == 0) {
			foreach my $line (@lines)
			{
				my $sock = new IO::Socket::INET ( PeerAddr => $ms ,
										  PeerPort => $port ,
										  Proto    => 'tcp'
				);								  
				die 
                MessageUtils->messageFromCat('csmInstall.cat', $::MSGMAPPATH, 
                                             'csminstall',     'E1',
                                             'EMsgCANT_CREATE_SOCKET', $!
                                             ) unless $sock;
				chomp $line;
				my @items=split(" ",$line);
				my $node = $items[0];
				my $msg = $items[1];
				my $status = $items[2];
				if (($msg eq "CSM_status_message") ||
				    ($msg eq "CSM_hwmaint_status_message"))
				{
					for(my $index=3;$index<@items;$index++)
					{
						$status="$status $items[$index]";
					}
				}
				syswrite($sock,"$node CSM_start_session\n");
				my $read;
				my $bytes_to_read=6;
				sysread($sock,$read,$bytes_to_read);
				chomp $read;
				if ( $read eq "ready" )
				{
			 		syswrite($sock, "$node $msg $status\n");
				}	
				$bytes_to_read=5;
				sysread($sock,$read,$bytes_to_read);
				chomp $read;
				close ($sock);
			}
			exit 0; }#if $child==0
		}	
	}	
}
sub write_msg
{
	my $pkg=shift;
	my $msg=shift;
	my $dir=$pkg->{_dir};
	my $recordFile = $pkg->{_hostsfile};#$::CSMSTATUSDIR . "/" . $::HOST_RECORD_FILE;
    if(!-e $recordFile)
	{
		NodeUtils->runcmd("touch $recordFile",-1);
	}	
	open (RECORD_FILE, ">>$recordFile") or MessageUtils->message('E2','EMsgCANT_WRITE_FILE',$recordFile);
    flock(RECORD_FILE, LOCK_EX) or MessageUtils->message('E2','EMsgCANT_LOCK_FILE',$recordFile);
    print RECORD_FILE "$msg\n";
    close(RECORD_FILE);
}
sub getcmd
{
	my $cmd="/opt/csm/csmbin/nodestat.awk";
	return $cmd;
}	
sub process_status_msg 
{
	my $pkg=shift;
	my $nodename=shift;
	my $line=shift;
	my $status=shift;
	my $cmd=getcmd;

	$pkg->write_status($nodename,$status);
	
}
sub process_hwmaint_msg 
{
	my $pkg=shift;
	my $nodename=shift;
	my $line=shift;
	my $status=shift;
	my $cmd=getcmd;

	$pkg->write_msg("$nodename $line $status");
	
}
sub process_disk_boot
{
	my $pkg=shift;
	my $nodename=shift;
	my $line=shift;
	my $status=shift;
	my $cmd=$pkg->getcmd;
	my $dir=$pkg->{_dir};			 
	my @nodes;

	NodeUtils->runcmd("touch $dir/csm/status/$nodename.rebootready",-1);
	push @nodes,$nodename;
	NodesetUtils->setupBoot("disk","onlyhex",@nodes);
}
sub process_min_comp
{
	my $pkg=shift;
	my $nodename=shift;
	my $line=shift;
	my $status=shift;
	my $cmd=$pkg->getcmd;
	
	$pkg->write_msg("$nodename $line $status");
}
sub process_stop_logging
{
	my $pkg=shift;
	my $msg_node=shift;
	my $nodename=shift;
	my $line=shift;
	my $status=shift;
	my $cmd=$pkg->getcmd;
	my @mgmtsvr=split(',',$::mgmtsvr);
 	
	if(grep(/^$msg_node$/,@mgmtsvr))
	{
		#If one node's installation timeout,atd on management server will send stop logging message to nodestatusd
		#with the node's ip address in the status variable 
		$msg_node=$status;
	}	
	if(	!( $pkg->update_allow_file($msg_node) ) )
	{ 
		$pkg->write_msg("$nodename $line $status");
		sleep 10;
		if(!$::restart)
		{
			my $parent=getppid;
			kill 'TERM', $::process_status;
			kill 'TERM', $parent;
			return 1;
		}	
	}
	return 0;

}
sub update_dir
{
	my $pkg=shift;
	my $dir_file="/tmp/CSMINSTALL_ROOT";
    	if(-e $dir_file)
	{
		open (DIRFILE, "<$dir_file") or MessageUtils->message('E2','EMsgCANT_OPEN_FILE',$$dir_file);
    		my @dir=<DIRFILE>;
		chomp $dir[0];
		$pkg->{_dir}=$dir[0];	
		close(DIRFILE);
		unlink $dir_file;
	}	
}
1;

package main;
use strict;

BEGIN
{
    $::csmpm = $ENV{'CSM_PM'} ? $ENV{'CSM_PM'} : '/opt/csm/pm';
    $ENV{'POSIXLY_CORRECT'} = 1;
}
use lib $::csmpm;
use IO::Socket;
use IO::File;
use POSIX qw(WNOHANG setsid WEXITSTATUS);
use Sys::Syslog qw(:DEFAULT setlogsock);
use Getopt::Std;
use NodeUtils;
use CSMDefs;
use MessageUtils;
use InstallKRB5Utils;
use NodesetUtils;

use constant PIDFILE => '/var/lock/csm/nodestatusd.pid';


$::MSGCAT     = 'csmInstall.cat';
$::MSGMAPPATH = $ENV{'CSM_MSGMAPS'} ? $ENV{'CSM_MSGMAPS'} : '/opt/csm/msgmaps';
$::MSGSET     = 'csminstall';

system("mkdir -p /var/lock/csm");

my $quit = 0;
my $node;

my $server_type="MS";		   
if($ENV{'SERVER'})
{
	$server_type=$ENV{'SERVER'};
}	
my $server_dir="/csminstall";
if ($ENV{'CSMINSTALL_ROOT'})
{	
	$server_dir=$ENV{'CSMINSTALL_ROOT'};
}	
if ($ENV{'MGMTSVR'})
{	
	$::mgmtsvr=$ENV{'MGMTSVR'};
}	
$::MS_PORT=$ENV{'MS_PORT'};
my $server=$server_type->new("$server_dir");
$server->update_dir;

#The caller must ensure the port is correct.
#Do not need to check
if(!getopts('p:'))
{
	print "usage: nodestatusd -p port\n";
	exit 1;
}
my $port;
$port=$::opt_p;

$SIG{CHLD} = 
sub 
{ 
	while (waitpid(-1,WNOHANG)>0){}; 
};
$SIG{TERM} = $SIG{INT} = sub { $quit++ };
$SIG{HUP} = sub { $::restart=1 };

$::debug=0;

$::NODESTATUSD_LOG="/var/log/csm/nodestatusd.log";
MessageUtils->start_logging($::NODESTATUSD_LOG);

my $listen_socket =
IO::Socket::INET->new(
	LocalPort => $port,
	Listen    => 20,
	Proto     => 'tcp',
	ReuseAddr     => 1,
	Timeout   => 60*60,
);
die
MessageUtils->messageFromCat('csmInstall.cat', $::MSGMAPPATH, 
                             'csminstall',     'E1', 
                             'EMsgCANT_CREATE_SOCKET', $!
                             ) unless $listen_socket;

my $fh = $server->lockfile(PIDFILE) ;
my $pid= $server->daemonic();
print $fh $pid;
close $fh; 

MessageUtils->message('I','IMsgWaitForConnection');

#Fork a separate process to process hosts file periodically.
my $child = fork();
if(!defined $child) {
    die MessageUtils->messageFromCat('csmInstall.cat', $::MSGMAPPATH,
                                     'csminstall',     'E1', 
                                     'EMsgCannotFork'
                                     );
}
if ($child == 0) {
	$listen_socket->close;
	while (!$quit)
	{
		$server->process_hosts($::mgmtsvr,$::MS_PORT);
	}
	exit 0;
}
	

$::process_status=$child;
$::restart=0;
while (!$quit) {
	if($::restart)
	{
		$::restart=0;
		$server->update_dir;
	}
	next unless my $connection = $listen_socket->accept;
	#Fork a child process to process the request.
	my $child = fork();

	if(!defined $child) {
        die MessageUtils->messageFromCat('csmInstall.cat', $::MSGMAPPATH,
                                         'csminstall',     'E1', 
                                         'EMsgCannotFork'
                                         );
    }
	if ( $child == 0) {
		my $ret;
		$listen_socket->close;
		my $node_ip = $connection->peerhost;
		MessageUtils->message('I','IMsgConnectionFromNode',$node_ip);
		$ret=$server->interact($connection,$node_ip);
		$connection->close ;
		exit $ret ;
	}

	$connection->close;
}

MessageUtils->stop_logging;

END { unlink PIDFILE if $$ == $pid; }



