#!/usr/bin/perl -w -I../lib/
#
# Perl IPTV-Analyzer collector daemon
#
=pod

=head1 NAME

iptv-collector -- IPTV-Analyzer Collector daemon

=head1 DESCRIPTION

The IPTV-Analyzer Collector daemon, parse and process data from
the iptables module mpeg2ts, via reading files under:

  /proc/net/xt_mpeg2ts/rule_XXX

The daemon constantly compares the proc file input, to detect changes
for a given multicast channel, e.g drop detection.  Changes are
recorded and stored into a MySQL database.

=head1 CONFIG

The config file: /etc/iptv-analyzer/collector.conf

Sample/documentation of the config format see:

  man IPTV::Analyzer::mpeg2ts

=cut

use strict;
use warnings;

use POSIX qw(setsid);
use sigtrap qw(handler sig_handler normal-signals);

use Data::Dumper;

use Proc::Daemon;    # libproc-daemon-perl
use Proc::PID::File; # libproc-pid-file-perl

use File::Basename;
use IPTV::Analyzer::mpeg2ts;
use IPTV::Analyzer::snmptrap;

# Trick to catch stderr messages from e.g. DB calls into Log4perl
use IPTV::Analyzer::Log4perlTrapper;

# Logger hack for changing the logging level runtime
use Log::Log4perl qw(get_logger :levels);
our $logger = get_logger("IPTV::Analyzer::mpeg2ts");
#$logger->level($DEBUG);

# Config options
use IPTV::Analyzer::Config;
our $cfg  = get_config();
our $interval = $cfg->{'collector_interval'} || 10;
our $heartbeat_interval_ticks = $cfg->{'collector_heartbeat_ticks'} || 60;
our $snmptraphost  = $cfg->{'snmptraphost'} || "87.72.129.222";
our $snmpcommunity = $cfg->{'snmpcommunity'} || "public";

# State variables
our $ticks = 0;
our $run = 1;

our $restart = 1;

# We need to TIE STDERR to log4perl, because the DB can report strange
# things.
#
# http://search.cpan.org/~mschilli/Log-Log4perl-1.26/lib/Log/Log4perl/FAQ.pm#Some_module_prints_messages_to_STDERR._How_can_I_funnel_them_to_Log::Log4perl?
#


sub daemon()
{
    my $pid;
    my $rundir  = "/var/run/";
    my $pidfile = $rundir . basename($0) . ".pid";
    if ( not -w $pidfile) {
	my $log = "Cannot write to PID file $pidfile";
	$logger->logcroak($log);
    }

#    open STDIN,  '< /dev/null' or die "Can't read /dev/null: $!";
#    open STDOUT, '> /dev/null' or die "Can't write /dev/null: $!";
#    open STDERR, '> /dev/null' or die "Can't write /dev/null: $!";

    # man fork(2): On success, the PID of the child process is
    #  returned in the parent, and 0 is returned in the child.
    #
    defined($pid = fork) or $logger->logcroak("Can't fork: $!");
    if ($pid) {
	open(PID, "> $pidfile")
	    or $logger->logcroak("Can't open pidfile $pidfile: $!");
	print PID $pid;
	close(PID);
	exit 0;
    }

    setsid()  or die "Can't start a new session: $!";
    chdir '/' or die "Can't chdir to /: $!";
    umask 0   or die "Can't umask: $!";

    open STDIN,  '< /dev/null' or die "Can't read /dev/null: $!";
    open STDOUT, '> /dev/null' or die "Can't write /dev/null: $!";
#   open STDERR, '> /dev/null' or die "Can't write /dev/null: $!";
}


sub daemon2()
{
    # Problem: perm to /var/run/$0.pid
    my $rundir  = "/var/run/";
    my $pidfile = $rundir . basename($0) . ".pid";
    if ( not -w $pidfile) {
	my $log = "Cannot write to PID file $pidfile";
	$logger->logcroak($log);
    }

    # Daemonize
    Proc::Daemon::Init();
    # After this line we cannot output to stderr/stdout

    # Check if already running
    my $otherpid = Proc::PID::File->running();
    if ($otherpid > 0) {
	my $log = "Daemon is already running as PID:[$otherpid]";

	if ($restart) {
	    my $cnt = kill("TERM", $otherpid);
	    if ($cnt < 1) {
		$log .= " - could not kill current exit self!";
		$logger->error($log);
		exit(2);
	    }
	    $log .= " - KILL and restart!";
	    $logger->warn($log);

	    sleep(1);
	    # Write new PID into pid file
	    if (Proc::PID::File->running()) {
		$logger->error("Tried killing $otherpid but failed! - exit");
		exit(3);
	    }

	} else {
	    $log .= " - exiting buy buy!";
	    $logger->warn($log);
	    exit(1);
	}
    } else {
	$logger->info("Starting IPTV::Analyzer collector daemon2");
    }
}


sub daemon3()
{
    # Problem: perm to /var/run/$0.pid
    my $rundir  = "/var/run/";
    my $pidfile = $rundir . basename($0) . ".pid";
    if ( -e $pidfile && (not -w $pidfile)) {
	my $log = "Cannot write to PID file $pidfile";
	$logger->logcroak($log);
    }

    open(STDIN,  "+>/dev/null");
    open(STDOUT, "+>&STDIN");
#    open(STDERR, "+>&STDIN");
#    tie *STDERR, "Trapper";


#    open STDIN,  '< /dev/null' or die "Can't read /dev/null: $!";
#    open STDOUT, '> /dev/null' or die "Can't write /dev/null: $!";
#    open STDERR, '> /dev/null' or die "Can't write /dev/null: $!";


    # Daemonize
    # man fork(2): On success, the PID of the child process is
    #  returned in the parent, and 0 is returned in the child.
    #
    my $pid;
    defined($pid = fork) or $logger->logcroak("Can't fork: $!");
    if ($pid) {
	exit(0);
    }

    setsid()  or die "Can't start a new session: $!";
    chdir '/' or die "Can't chdir to /: $!";
    umask 0   or die "Can't umask: $!";

#   open STDIN,  '< /dev/null' or die "Can't read /dev/null: $!";
#   open STDOUT, '> /dev/null' or die "Can't write /dev/null: $!";
#   open STDERR, '> /dev/null' or die "Can't write /dev/null: $!";

    # Check if already running
    my $otherpid = Proc::PID::File->running();
    if ($otherpid > 0) {
	my $log = "Daemon is already running as PID:[$otherpid]";

	if ($restart) {
	    my $cnt = kill("TERM", $otherpid);
	    if ($cnt < 1) {

		# Trying to kill again after a delay
		my $delay = 5;	# = $interval (?)
		my $logtry = "could not kill, trying again in $delay sec";
		$logger->error("$log - $logtry");
		sleep($delay);
		# Test if the process is running before killing it again
		# (kill with signal 0 is just a test)
		if ($otherpid != $$ && kill(0, $otherpid)) {
		    $cnt = kill("TERM", $otherpid);
		    if ($cnt < 1) {
			# Giving up
			$log .= " - could NOT kill current daemon, exit self!";
			$logger->fatal($log);
			exit(2);
		    }
		}
	    }
	    my $delaydb = 3;
	    $log .= " - KILL and restart (in $delaydb sec)";
	    $logger->warn($log);

	    # Wait a bit for the other process to finish db sessions
	    sleep($delaydb);
	    # Write new PID into pid file
	    if (Proc::PID::File->running()) {
		$logger->fatal("Tried killing pid:$otherpid but failed! - exit");
		exit(3);
	    }

	} else {
	    $log .= " - exiting buy buy!";
	    $logger->warn($log);
	    exit(1);
	}
    } else {
	my $ver = $IPTV::Analyzer::mpeg2ts::VERSION;
	$logger->info("Started IPTV::Analyzer collector daemon (child) (version:$ver)");
    }
}


sub sig_handler {
    $logger->info("Going to exit due to kill signal");
    heartbeat_update();
    close_daemon_sessions();
    close_stream_sessions();
    close_snmp_session();
    $run = 0;
}

#http://search.cpan.org/~mschilli/Log-Log4perl-1.26/lib/Log/Log4perl/FAQ.pm#My_program_already_uses_warn%28%29_and_die%28%29._How_can_I_switch_to_Log4perl?

#$SIG{__WARN__} = sub {
#    local $Log::Log4perl::caller_depth = $Log::Log4perl::caller_depth + 1;
###    WARN @_;
####    $logger->warn(@_) if defined @_;
#    warn(@_) if defined @_;
#};

$SIG{__DIE__} = sub {
    if($^S) {
	# We're in an eval {} and don't want log
	# this message but catch it later
	return;
    }
    $Log::Log4perl::caller_depth++;
#    LOGDIE @_;
    $logger->logdie(@_);
};


#tie *STDERR, "Trapper";
#tie *STDERR, "IPTV::Analyzer::mpeg2ts";

## Testing outputs
#print        "TEST1:stdout\n";
#print STDERR "TEST1:stderr\n";
#warn         "TEST1:warn()\n";

my $ver = $IPTV::Analyzer::mpeg2ts::VERSION;
$logger->info("Starting IPTV::Analyzer collector daemon (parent) (version:$ver)");


# Fork of this process
#daemon();
#daemon2();
daemon3();

## Testing output after the child process has been forked
#print        "TEST2:stdout\n";
#print STDERR "TEST2:stderr\n";
#warn         "TEST2:warn()\n";

# IPTV::Analyzer::mpeg2ts
db_connect();

open_snmp_session($snmptraphost, $snmpcommunity);

# The loop
while ($run) {

    # Process all inputs according to the config file
    process_inputs();

    sleep($interval);
    $ticks++;

    if (($ticks % $heartbeat_interval_ticks) == 0) {
	heartbeat_state_active();
	heartbeat_update();
    } else {
	heartbeat_state_clear();
    }
}

$logger->info("Stopping IPTV::Analyzer collector daemon (version:$ver)");

db_commit();
db_disconnect();
close_snmp_session();
exit;

__END__
=pod

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2009-2011+ by Jesper Dangaard Brouer, ComX Networks A/S.

This file is licensed under the terms of the GNU General Public
License 2.0. or newer. See <http://www.gnu.org/licenses/gpl-2.0.html>.

=cut
