#!/usr/local/bin/perl -w
#
#  Copyright (c) 2004, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#     this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#     this list of conditions and the following disclaimer in the documentation
#     and/or other materials provided with the distribution.
#   * Neither the name of SWITCH nor the names of its contributors may be
#     used to endorse or promote products derived from this software without
#     specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#  POSSIBILITY OF SUCH DAMAGE.
#
#  $Author: peter $
#
#  $Id: nfsend 59 2006-08-10 17:47:53Z peter $
#
#  $LastChangedRevision: 59 $

use strict;
use warnings;
use POSIX 'setsid';
use POSIX ":sys_wait_h";
use Sys::Syslog;
Sys::Syslog::setlogsock('unix');

use Data::Dumper;

######################################
#
# Configuration: 
# The only parameter to set:

use lib "/usr/local/libexec/nfsen";

#
######################################

use NfSen;
use NfSenRRD;
use NfConf;
use Nfsources;
use Nfcomm;
use Log;

my $VERSION = '$Id: nfsend 59 2006-08-10 17:47:53Z peter $';
my $nfsen_version = "snapshot-20060810";

my $forever = 1;
my $reload  = 0;
my %plugin_table;

my $unit = '';
sub SetSignalHandlers {
	$SIG{TERM} = sub { syslog('info', "$unit: Got SIGTERM"); $forever = 0; };
	$SIG{HUP}  = sub { syslog('info', "$unit: Got SIGHUP");  $forever = 0; $reload = 1; };
	$SIG{USR1} = sub { syslog('info', "$unit: Got SIGUSR1"); $forever = 0; $reload = 1; };
	$SIG{INT}  = sub { syslog('info', "$unit: Got SIGINT");  $forever = 0; };
} # End of SetSignalHandlers

# we need a global var for the launcher
# otherwise we have no access from sub __DIE__

my $launcher_var = undef;	
my $launcher_pid = undef;		# pid of launcher
my $comm_pid	 = undef;		# pid of comm server
my $waitedpid 	 = 0;
my $pidfile		 = 'nfsend.pid';

$SIG{'__DIE__'} = sub {
	my $why = shift;
	syslog('err', "PANIC $unit dies: $why");
	if ( $unit ne "nfsend" ) {
		return;
	}

	if ( $launcher_pid && kill( 0, $launcher_pid) ) {
		syslog('debug',"Signal launcher to terminate");
		kill 'TERM', $launcher_pid || warn "Can't signal comm server: $! ";
		# print $launcher_var ":DONE:\n";
		wait;	# for Launcher to terminate
	}
	if ( $comm_pid && kill( 0, $comm_pid) ) {
		syslog('debug',"Signal comm server to terminate");
		kill 'TERM', $comm_pid || warn "Can't signal comm server: $! ";
		wait;	# for comm server to terminate
	}
	unlink "$NfConf::VARDIR/run/nfsend.pid";
};

sub SIG_CHILD {
    my $child;
	my $which_child   = undef;
	my $expected;

	if ( $forever ) {
		$expected = 'unexpected';
	} else {
		$expected = 'expected';
	}

    while (($waitedpid = waitpid(-1,WNOHANG)) > 0) {
		my $ret = $? ? "returned: $?" : '';
		if ( $launcher_pid && $waitedpid == $launcher_pid ) {
			$which_child = "Launcher";
		}
		if ( defined $comm_pid && $waitedpid == $comm_pid ) {
			$which_child = "Comm Server";
		}

		syslog('err', "$expected exit of child $which_child" ) if defined $which_child;
		if ( $which_child && $forever ) {
        	syslog('err', "Try to restart NfSen");
			$forever = 0; 
			$reload = 1;
		} else {
			syslog('debug', "$unit: exit child[$waitedpid] $ret");
		}
	
    }
    $SIG{CHLD} = \&SIG_CHILD;  # loathe sysV

} # End of SIG_CHILD

sub daemonize {
	my $pidfile = shift;

	chdir '/'				  || die "Can't chdir to /: $!";
	open STDIN, '/dev/null'   || die "Can't read /dev/null: $!";
	open STDOUT, '>/dev/null' || die "Can't write to /dev/null: $!";
	open STDERR, '>/dev/null' || die "Can't write to /dev/null: $!";
	defined(my $pid = fork)   || die "Can't fork: $!";
	if ( $pid ) { 
		# we are the parent processs
		if ( defined $pidfile ) {
			unlink $pidfile;
			open PID, ">$pidfile" || die "Can't open pid file: $!\n";
			print PID "$pid\n";
			if ( !close PID ) {
				my $err = $!;
				kill 15, $pid;
				syslog('err', "Error startup nfsend: $err");
				die "Unable to write PID file: $err";
			}
		}
		exit;
	}
	setsid or die "Can't start a new session: $!";

} # End of daemonize

sub CheckLockLive {
	my %profileinfo = NfSen::ReadProfile('live');

	if ( $profileinfo{'locked'} ) {
		syslog('err', "ERROR profile 'live' locked! Unlock for clean startup!");
		$profileinfo{'locked'} = 0;
		if ( !NfSen::WriteProfile(\%profileinfo) ) {
			syslog('err', "Error writing profile 'live': $Log::ERROR");
		}
	} 

} # End of UnlockLive

sub CheckSubHierarchy {
	my %profileinfo = NfSen::ReadProfile('live');
	
	if ( $profileinfo{'size'} == 0 ) {
		return;
	}

	my @sources	 = split /:/, $profileinfo{'sourcelist'};
	my $source	 = shift @sources;
	my $t_last   = $profileinfo{'updated'};
	my $t_iso    = NfSen::UNIX2ISO($t_last);
    my $subdirs  = NfSen::SubdirHierarchy($t_last);
	my $filename = "$NfConf::PROFILEDATADIR/live/$source/$subdirs/nfcapd.$t_iso";

	if ( ! -f $filename ) {
		syslog('err', "Verification sub hierarchy failed. Expected file '$filename' does not exist!");
		syslog('err', "This may indicate an inconsitency between configured sub hierarchy layout and real layout.");
		syslog('err', "Rerun RebuildHierarchy.pl to fix.");

		print "\nVerification sub hierarchy failed. Expected file '$filename' does not exist!\n";
		print "This may indicate an inconsitency between configured sub hierarchy layout and real layout.\n";
		print "Rerun RebuildHierarchy.pl to fix.\n";
		print "nfsend not started.\n";
		exit(0);
	}

} # End of CheckSubHierarchy

sub Periodic {
	my $timeslot 		= shift;
	my $launcher_pipe 	= shift;

	syslog('debug', "Run periodic at " . scalar localtime($timeslot));

	my $t_iso = NfSen::UNIX2ISO($timeslot);
	my $subdirs = NfSen::SubdirHierarchy($timeslot);

	my @AllProfiles = NfSen::ProfileList();
	if ( scalar @AllProfiles == 0 ) {
		syslog('err', $Log::ERROR) if defined $Log::ERROR;
		return;
	}

	# Loop over all available profiles
	foreach my $profilename ( @AllProfiles ) {
		# not a valid profile -> continue
		next unless NfSen::InfoFileExists($profilename);

		my %profileinfo = NfSen::LockProfile($profilename);
		# Make sure profile is not empty - means it exists and is not locked
		if ( $profileinfo{'status'} eq 'empty' ) {
			# profile already locked and in use
			next if $profileinfo{'locked'} == 1;
		
			# it's an error reading this profile
			if ( defined $Log::ERROR ) {
				syslog('err', "Error $profilename: $Log::ERROR");
				next;
			}
		}

		# history profiles need to be updated only once, just after created
		if ( $profileinfo{'type'} == 1 && $profileinfo{'updated'} > $profileinfo{'tstart'} ) {
			$profileinfo{'locked'} = 0;
			if ( !NfSen::WriteProfile(\%profileinfo) ) {
				syslog('err', "Error writing profile '$profilename': $Log::ERROR");
			}
			next;
		}
		
		# remember if the profile may grow
		my $growing = $profileinfo{'type'} == 0 || $profileinfo{'type'} == 2;

		syslog('info', "Update profile $profilename");

		# At time $timestamp we have the follwing data avilable:
		# profile life: $timestamp - 300	( contains data from $timestamp - 300 .. $timestamp )
		# other profiles $timestamp - 600	( nfprofile needs one cycle )
		my $delta = $profilename eq 'live' ? 300 : 600;

		my $t_lowlimit = 946681200;	# Sat Jan  1 00:00:00 2000	- just a limit to prevent loops over buggy dates

		if ( $profileinfo{'updated'} < $t_lowlimit ) {
			syslog('err', "Timestamp updated '$profileinfo{'updated'}' => %s seems to be wrong. Skip profile.", scalar localtime($profileinfo{'updated'}));
			next;
		}

		for ( my $timestamp = $profileinfo{'updated'} + 300; $timestamp <= ($timeslot - $delta); $timestamp += 300 ) {
			my $slot_size   = 0;
			my %statinfo	= ();
			my $files_found = 0;
			my $t_iso = NfSen::UNIX2ISO($timestamp);
			my $subdirs = NfSen::SubdirHierarchy($timestamp);

			# get the stats from the timeslice
			foreach my $source ( split /:/, $profileinfo{'sourcelist'} ) {
				my $filename = "$NfConf::PROFILEDATADIR/$profilename/$source/$subdirs/nfcapd.$t_iso";

				# syslog('debug', "Expected file not found: $filename") unless  -f $filename;
	
				# there is no such file - nothing to do
				next unless -f $filename;

				$files_found = 1;

				$statinfo{$source} = NfSen::ReadStatInfo(\%profileinfo, $source, $subdirs, $t_iso);
				if ( defined $Log::ERROR ) {
					syslog('err', $Log::ERROR);
				} else {
					# sum up the total size of the profile with this file(s)
					# the true disk usage is important and not the size in bytes
					# therefore we take the allocated diskblocks to sum up
					$profileinfo{'size'} += 512 * (stat($filename))[12];
				}
			}
			next unless $files_found;

			# we have found some files at this timestamp => update
			$profileinfo{'updated'} = $timestamp;

			# update each RRD database
			foreach my $source ( split /:/, $profileinfo{'sourcelist'} ) {
				my @_values = ();
				foreach my $ds ( @NfSenRRD::RRD_DS ) {
                    if ( !defined $statinfo{$source}{$ds} || $statinfo{$source}{$ds} == - 1 ) {
                        push @_values, 0;
                    } else {
                        push @_values, $statinfo{$source}{$ds};
                    }
				}
				my $err = NfSenRRD::UpdateDB("$NfConf::PROFILESTATDIR/$profilename", $source, $timestamp,
					join(':',@NfSenRRD::RRD_DS) , join(':', @_values));
				if ( $Log::ERROR ) {
					syslog('err', "ERROR Update RRD time: '$t_iso', db: '$source', profile: '$profilename': $Log::ERROR");
				}

			}

			# give the plugins some CPU cycles
			if ( $profileinfo{'status'} eq 'OK' && defined $launcher_pipe ) {
				syslog('debug', "Signal launcher: $profilename:$t_iso");
				print $launcher_pipe "$profilename:$t_iso\n";
			}

		}
		# update the end of the profile for 'live' and 'continuous' profiles but not
		# for 'history' profiles
		$profileinfo{'tend'} = $profileinfo{'updated'} if $growing;

		# keep profile locked, if an error occured
		$profileinfo{'locked'} = 0 unless defined $Log::ERROR;	

		# Update the graphs if it's a growing profile
		if ( $profileinfo{'status'} eq 'OK' && $growing ) {
			NfSenRRD::UpdateGraphs($profilename, $profileinfo{'updated'}, 0);
			if ( defined $Log::ERROR ) {
				syslog('err', "Error graph update: $Log::ERROR");
			}
		}

		# reset profile status, if we rebuilt the profile
		if ( $profileinfo{'status'} eq 'rebuild' ) {
			# After a rebuild or after creating a profile, we need to create the graphics
			# for that profile. As history profiles may end at any selected time back in the past
			# we use the 'tend' timestamp, which is in any case the most recent timestamp for this profile
			NfSenRRD::UpdateGraphs($profilename, $profileinfo{'tend'}, 1);
			if ( defined $Log::ERROR ) {
				syslog('err', "Error graph update: $Log::ERROR");
			}
			$profileinfo{'status'} = 'OK';
			syslog('info', "Rebuild profile '$profilename' completed");
		}
		if ( !NfSen::WriteProfile(\%profileinfo) ) {
			syslog('err', "Error writing profile '$profilename': $Log::ERROR");
		}

	}

	syslog('debug', "End periodic at " . scalar localtime($timeslot));

} # end of Periodic 

sub ExpireProfiles {
	my $timeslot = shift;

	syslog('info', "Run expire at " . scalar localtime($timeslot));

	my @AllProfiles = NfSen::ProfileList();
	if ( scalar @AllProfiles == 0 ) {
		syslog('err', $Log::ERROR) if defined $Log::ERROR;
		return;
	}

	# Loop over all available pofiles
	foreach my $profilename ( @AllProfiles ) {
		# not a valid profile -> continue
		next unless NfSen::InfoFileExists($profilename);

		# stat info for expiring
		my $stat_filenum   = 0;	# number of files expired
		my $stat_filesize  = 0;	# total disksize expired
		my $stat_timerange = 0;	# time range expired

		my %profileinfo = NfSen::LockProfile($profilename);
		# Make sure profile is not empty - means it exists and is not locked
		if ( $profileinfo{'status'} eq 'empty' ) {
			# profile already locked and in use
			next if $profileinfo{'locked'} == 1;
		
			# it's an error reading this profile
			if ( defined $Log::ERROR ) {
				syslog('err', "Error $profilename: $Log::ERROR");
				next;
			}
		}

		# Nothing to do for history profiles
		if ( $profileinfo{'type'} == 1 ) {
			$profileinfo{'locked'} = 0;
			if ( !NfSen::WriteProfile(\%profileinfo) ) {
				syslog('err', "Error writing profile '$profilename': $Log::ERROR");
			}
			next;
		}

        if ( $profileinfo{'maxsize'} == 0 &&  $profileinfo{'expire'} == 0 ) {
        	# profile does not want any auto expire
        	$profileinfo{'locked'} = 0;
			if ( !NfSen::WriteProfile(\%profileinfo) ) {
				syslog('err', "Error writing profile '$profilename': $Log::ERROR");
			}
        	next;
        }
        #
        
		syslog('info', "Expire profile $profilename");
		# size unlimited profiles have maxsize = 0 - so don't care
		# otherwise maxsize contains the maximum size in bytes
		if ( $profileinfo{'maxsize'} && 
			($profileinfo{'size'} > $profileinfo{'maxsize'}) ) {
			my $timestamp		= $profileinfo{'tstart'};
			my $profilesize 	= $profileinfo{'size'};
			my $low_water_mark	= $profileinfo{'maxsize'} * $NfConf::low_water;


			# loop until profile size is less then low water mark
			while ( $profilesize > $low_water_mark ) {
				my $t_iso   = NfSen::UNIX2ISO($timestamp);
				my $subdirs = NfSen::SubdirHierarchy($timestamp);

				# Delete this timeslot in all sources
				foreach my $source ( split /:/, $profileinfo{'sourcelist'} ) {
					my $filename = "$NfConf::PROFILEDATADIR/$profilename/$source/$subdirs/nfcapd.$t_iso";
					next unless -f $filename;

					$profilesize -= 512 * (stat($filename))[12];
					$profilesize -= 512 * (stat("${filename}.stat"))[12] if -f "${filename}.stat";
					unlink $filename;
					unlink "${filename}.stat" if -f "${filename}.stat";
					$stat_filenum += 1;
				}
				$timestamp += 300;

				#
				# panic exit: if we still loop and we reached the end of the profile
				# or the current timeslot
				# this should never happen
				if ( $timestamp > $profileinfo{'tend'} ||
					 $timestamp > $timeslot ) {
					syslog('err', "Profile Size: Tried to expire beyond end of profile: " .  
						scalar localtime($timestamp));
					syslog('err', "Max: $profileinfo{'maxsize'}, Size: $profilesize, Low: $low_water_mark");
					my $val = Dumper(%profileinfo);
					print STDERR "$val\n";
					last;
				}
			}
			$stat_filesize 			= $profileinfo{'size'} - $profilesize;
			$profileinfo{'size'}	= $profilesize;
			$stat_timerange 		= $timestamp - $profileinfo{'tstart'};
			$profileinfo{'tstart'} 	= $timestamp;
		} else {
			syslog('debug', "No size limit reached");
		}

		# time unlimited profiles have expire = 0 - so don't care
		# otherwise expire contains the max life time in hours
		if ( $profileinfo{'expire'} && 
			($profileinfo{'tstart'} < ($timeslot - (3600*$profileinfo{'expire'})) ) ) {

			my $timestamp		= $profileinfo{'tstart'};
			my $profilesize 	= $profileinfo{'size'};
			while ( $timestamp < ($timeslot - (3600*$NfConf::low_water*$profileinfo{'expire'})) ) {
				my $t_iso = NfSen::UNIX2ISO($timestamp);
				my $subdirs = NfSen::SubdirHierarchy($timestamp);

				# Delete this timeslot in all sources
				foreach my $source ( split /:/, $profileinfo{'sourcelist'} ) {
					my $filename = "$NfConf::PROFILEDATADIR/$profilename/$source/$subdirs/nfcapd.$t_iso";
					next unless -f $filename;

					$profilesize -= 512 * (stat($filename))[12];
					$profilesize -= 512 * (stat("${filename}.stat"))[12] if -f "${filename}.stat";
					unlink $filename;
					unlink "${filename}.stat";
					$stat_filenum += 1;
				}

				$timestamp += 300;
				#
				# panic exit: if we still loop and we reached the end of the profile
				# or the current timeslot
				if ( $timestamp > $profileinfo{'tend'} ||
					 $timestamp > $timeslot ) {
					syslog('err', "Profile lifetime: Tried to expire beyond end of profile: " .
						scalar localtime($timestamp));
					last;
				}
			}
			$stat_filesize 			+= $profileinfo{'size'} - $profilesize;
			$profileinfo{'size'}	=  $profilesize;
			$stat_timerange 		+= $timestamp - $profileinfo{'tstart'};
			$profileinfo{'tstart'} 	=  $timestamp;
		} else {
			syslog('debug', "No time limit reached");
		}

		$profileinfo{'locked'} = 0;
		if ( !NfSen::WriteProfile(\%profileinfo) ) {
			syslog('err', "Error writing profile '$profilename': $Log::ERROR");
		}

		if ( $stat_filenum ) {
			my ($h, $m);
			$h = int($stat_timerange / 3600 );
			$m = ($stat_timerange % 3600) / 60;
			syslog('info', "Expire stat profile $profilename");
			syslog('info', "    Expired files : $stat_filenum");
			syslog('info', "    Expired size  : $stat_filesize");
			syslog('info', join '',"    Expired timeframe : $h hour" , $h == 1 ? " " : "s " , "$m minutes");
		} else {
			syslog('info', "Nothing to expire");
		}
	}

	syslog('info', "End expire at " . scalar localtime($timeslot));

} # End of ExpireProfiles

sub load_module {
	my $module = shift;

	eval {
		local $SIG{'__DIE__'} = 'DEFAULT';
		require "$module.pm";
	};

	if ( my $err = $@ ) {
		syslog('err', "Loading plugin '$module': Failed.");
		syslog('err', "ERROR: $err");
		return 0;
	}
	syslog('info', "Loading plugin '$module': Success");

	my $ret = eval {
		local $SIG{'__DIE__'} = 'DEFAULT';
		$module->Init();
	};
	if ( $@ ) {
		syslog('err', "Initializing plugin '$module': Module died: $@");
		return 0;
	} else {
		if ( $ret ) {
			syslog('info', "Initializing plugin '$module': Success");
			return 1;
		} else {
			syslog('warning', "Initializing plugin '$module': Suspended");
			return 0;
		}
	}


} # End of load_module

sub Launcher {

	# communicate via pipes, so the plugins will not block
	# the main cycle
	my ($READER, $WRITER);
	pipe($READER, $WRITER);

	if ( $launcher_pid = fork) {
		# we are the parent
		close $READER;

		# make it unbuffered
		my $_tmp = select $WRITER;
		$| = 1;
		select $_tmp;

		syslog("debug", "Launcher started: [$launcher_pid]");
		return $WRITER;
	
	} else {
		# we are the child
		setsid or die "Launcher: can't start a new session: $!";
		$unit = "Launcher";
		close $WRITER;
	
		SetSignalHandlers();
		$SIG{'__DIE__'} = sub {
			my $why = shift;
			syslog('err', "PANIC launcher dies: $why");
		};
		$SIG{CHLD} = \&SIG_CHILD;
		# That's were we find all the plugins
		# make warnings happy, as $NfConf::BACKEND_PLUGINDIR is used once only

		no warnings;
		unshift @INC, "$NfConf::BACKEND_PLUGINDIR";
		use warnings;

   		# Load all plugins
		foreach my $plugin ( @NfConf::plugins ) {
			my $profilelist = $$plugin[0];
			my $module		= $$plugin[1];
			my @Profiles;

			# Check and report frontend plugin file
			if ( -f "$NfConf::FRONTEND_PLUGINDIR/${module}.php" ) {
				syslog('info', "Frontend module '${module}.php' found");
			}

			# Check and report backend plugin file
			if ( !-f "$NfConf::BACKEND_PLUGINDIR/${module}.pm" ) {
				syslog('info', "No backend module '${module}.pm' found");
				next;
			}

			# Try to load and initialize the module
			next unless load_module($module);

			if ( $profilelist eq '*' ) {
				@Profiles = NfSen::ProfileList();
				if ( scalar @Profiles == 0 ) {
					syslog('err', $Log::ERROR) if defined $Log::ERROR;
				}
			} else {
				@Profiles = split /\s*,\s*/, $profilelist;
			}

			foreach my $profile ( @Profiles ) {
				if ( ! NfSen::InfoFileExists($profile )) {
					syslog('err', "Register plugin '$module': Profile '$profile' does not exists!");
					next;
				}
				if ( exists $plugin_table{$profile} ) {
					$plugin_table{$profile} .= ",$module";
				} else {
					$plugin_table{$profile}	= "$module";
				}
			}
		}
		foreach my $key ( keys %plugin_table ) {
			syslog('info', "ModList: $key - $plugin_table{$key}");
		}
 		#
		# now read from parent
		#
		while ( $forever ) {
			my $buf;
			eval {
				local $SIG{'__DIE__'} = 'DEFAULT';
				local $SIG{TERM} = sub { $forever = 0; die; };
				$buf = <$READER>;
			};
			if ($@) {
				syslog('info', "Launcher: interrupted");
				next;
			} 
			if ( !$buf ) {
				# hmm .. should never happer, but catch it anyway
				if ( eof($READER) ) {
					# again - this should never happen
					# maybe ist's best to restart nfsend
					$forever = 0;
					next;
				} else {
					# next read should be ok.
					next;
				}
			}
			chomp $buf;
			if ( !defined $buf ) {
				syslog('err', "Launcher: received undef. Assume parent died.");
				$forever = 0;
				last;
			}
			last if $buf =~ /:DONE:/;
			my ( $profile, $timeslot ) = split ':', $buf;
			syslog('debug', "Launcher Cycle: received: $profile, $timeslot");
			next unless exists $plugin_table{$profile};

			my $modules = $plugin_table{$profile};
			foreach my $module ( split ',', $modules ) {
				syslog("info", "Launcher Cycle: Time: $timeslot, Profile: $profile, Module: $module, ");
				my $sub = "${module}::run";
				no strict 'refs';
				eval {
					local $SIG{'__DIE__'} = 'DEFAULT';
					&$sub($profile, $timeslot);
				};
				use strict 'refs';
				if ( $@ ) {
					syslog("err", "Launcher: Error while running plugin '$module': $@ ");
				}
			}
		}

		# we are done
		syslog("debug", "Launcher exit");
		exit 0;
	}

} # End of Lauchner

######################
#
# Main starts here
#
######################
	

if ( !NfConf::LoadConfig() ) {
	die "$Log::ERROR\n";
}

if ( Nfsources::CheckReconfig() == 0 ) {
	exit;
}

Log::LogInit();
syslog("info", "Startup. Version: $nfsen_version $VERSION");
$unit = "nfsend";

my $arg = shift @ARGV;
$arg = '' unless defined $arg;
die "Unknow argument '$arg'" if $arg ne '' && $arg ne 'once';

if ( !NfSen::DropPriv($NfConf::USER) ) {
	die "$Log::ERROR\n";
}


my $launcher_pipe = undef;
$forever = ($arg eq 'once') ? 0 : 1;
# When starting up, check if all the profiles are up to date till the last timeslot
# then periodically update the profiles
if ( $forever ) {
	if ( -f "$NfConf::VARDIR/run/$pidfile" ) {
		# there is a pid file .. check for the process
		open PID, "$NfConf::VARDIR/run/$pidfile" || 
			die "Can't read pid file '$NfConf::VARDIR/run/$pidfile': $!\n";
		my $pid = <PID>;
		chomp $pid;
		close PID;
		if ( kill( 0, $pid) == 0  ) {
			print "Unclean shutdown of nfsend[$pid]\n";
			unlink "$NfConf::VARDIR/run/$pidfile";
		} else {
			print "nfsend[$pid] already running\n";
			exit;
		}
	}
	# still as a single process check for proper status of profile 'live'
	CheckLockLive();

	# Check if configured sub hierarchy matches
	CheckSubHierarchy();

	daemonize("$NfConf::VARDIR/run/$pidfile");
	# Check for plugins and fork the laucher process
	if ( scalar @NfConf::plugins > 0 ) {
		$launcher_pipe = Launcher();
	}

    if (!defined($comm_pid = fork)) {
        print STDERR "cannot fork: $!";
        die "Died";
    } elsif ($comm_pid) {
		# I'm the parent process
        syslog('info', "nfsend: [$$]");
    } else {
		# I'm the child process
		setsid or die "Comm server: Can't start a new session: $!";
		$unit = "Comm server";

		# Start socket server
		my $server = Nfcomm::Setup_Server();
		if ( !defined $server ) {
			syslog("err", "Can't start comm server: ");
			die "Died";
		}

		syslog("info", "Comm server started: [$$]");
		Nfcomm::RunServer($server);
		Nfcomm::Close_server($server);
		syslog("info", "Comm server terminated: [$$].");
		exit(0);
	}

	
} else {
	syslog("info", "Run '$arg'");
}

SetSignalHandlers();
$launcher_var = $launcher_pipe;
$SIG{CHLD} = \&SIG_CHILD;

tie(*STDERR, 'Log', 'nfsen');

my ( $now, $lastcycle );
do {
	$now = time();
	$lastcycle = $now - ( $now % 300 );

	Periodic($lastcycle, $launcher_pipe);
	ExpireProfiles($lastcycle);
	
	#
	# determin, if we need some sleep. If we are done in this cycle
	# sleep until next timeslot + 30s = 330s
	# otherwise we are behind time schedule. continue updating the 
	# profiles until we are on track and can sleep
	$now = time();
	if ( ($now - $lastcycle) < 330 ) {
		my $sleeptime = $lastcycle + 330 - $now;
		sleep $sleeptime if $forever;
	} else {
		syslog('warning',"Behind schedule");
	}

} while ( $forever );

my $kid;
if ( $launcher_pid && kill( 0, $launcher_pid) ) {
	syslog('debug',"Signal launcher to terminate");
	kill 'TERM', $launcher_pid || syslog('err', "Can't signal launcher: $!");
	# wait for Launcher to terminate
	do {
		select(undef, undef, undef, 0.25);
		$kid = kill( 0, $launcher_pid);
	} while $kid == 1;

}
if ( $comm_pid && kill( 0, $comm_pid) ) {
	syslog('debug',"Signal comm server to terminate");
	kill 'TERM', $comm_pid || syslog('err', "Can't signal comm server: $!");
	# wait for comm server to terminate
	do {
		select(undef, undef, undef, 0.25);
		$kid = kill( 0, $comm_pid);
	} while $kid == 1;
}

# cleanup
if ( $arg ne 'once' && -f "$NfConf::VARDIR/run/$pidfile" ) {
	unlink "$NfConf::VARDIR/run/$pidfile";
}

# shut up warning. The statemants after exec are valid.
no warnings;
if ( $reload ) {
	if ( $0 =~ /^\// ) {
		syslog("info", "Restart $0");
		exec $0;
		syslog("err", "Can't restart self: '$0': $!");
	}
	syslog("info", "Restart $NfConf::BINDIR/nfsend");
	exec "$NfConf::BINDIR/nfsend";
	syslog("err", "Can't restart self: '$NfConf::BINDIR/nfsend': $!");
} else {
	syslog("info", "Exit");
}

untie *STDERR;
Log::LogEnd;

exit 0;
