#!/usr/bin/perl -w 

use strict;
use Data::Dumper;

$|=1;

=info
Author                  : Sanjoga Sahu.
Date of Modification    : 03rd, Dec, 2010. (v1.0.0)

Operating System(s)     : Linux

Description             : Runs jobs line crontab.

---------------------------------------------------------------------
Execution Method        :

Options:
    -h : Help
    -d : Debug Printing Mode
    -c : Config file to be used	(No Space between the arguments) [Also Works with Options [config= | conf=]
         Ex: i.   -c/abc/xyz/mycron.conf 
           	 ii.  conf=/abc/xyz/mycron.conf
             iii. config=/abc/xyz/mycron.conf	  

Execution Method :
    1) Running Script Normally (No debug Printing log)... 
        nohup cron_own.pl &

    2) Running Debug Printing Mode (Prints log)... 
        nohup cron_own.pl -d >>! \$HOME/cron_own.log &

	3) Running Script Normally using diffrent config file (No debug Printing log)... 
        nohup cron_own.pl config=/abc/xyz/mycron.conf &

---------------------------------------------------------------------

Config Information      :

Create a config file at : $HOME/config/cron_own.conf

Config Entries are like contab...
	Example:
	Minute   Hour   Day of Month       Month          Day of Week        Command    
	(0-59)  (0-23)     (1-31)    (1-12 or Jan-Dec)  (0-6 or Sun-Sat)                
	0        2          12             *               0,6           /usr/bin/find

Example: (Run in every 00 min of an Hour)

00 * * * * $HOME/MirrorUp_genius_1.0.1.pl I=no >> $HOME/MirrorUp.out 2>&1

---------------------------------------------------------------------

How to check the process and stop script:
#ps -elf | grep cron

0 S XXXXXXXX  27196 26725  0  78   0 -  5759 -      00:04 pts/2    00:00:00 /usr/bin/perl -w cron_own.pl

#kill -9 27196

Will Display:
[1]    Killed                        cron_own.pl >> $HOME/cron_own.log

---------------------------------------------------------------------
=cut

my %INFO=();

my $config="$ENV{'HOME'}/config/cron_own.conf";

my $opt=lc(&trim($ARGV[0]));

my $debug=0;
my $c_flg=0;

	if(defined $opt)
	{
		foreach my $opt_e (@ARGV)
		{
			if($opt_e eq '-h')
			{
			&usage(0);
			exit(0);
			}
			if($opt_e eq '-d')
			{
			$debug=1;
			}
			if($opt_e=~ /(config=|conf=|-c)(\S+)/)
			{
			$config=$2;
			}
			if($opt_e=~ /-c|conf/)
			{
			$c_flg=1;
			}
		}
	}

print "\n" if($debug==1);

	unless(-f $config)
	{
	print "\n[$0] Configuration File NOT Found, Please Create [$config]\n";
	&usage();
	exit(1);
	}
	
	if($c_flg)
	{
	print "[$0] Configuration File Used [$config]\n";
	}
	
my $run=1;

#Infinite While loop which reads the config file in every minute  and run them if the run conditions are met.

	while(1) 
	{
	my $machine=&trim(`uname -n`);
	my $process=$$;
	my $TimeStamp=`date +[%Y-%m-%d' '%H:%M:%S`;
	chomp $TimeStamp;
	$TimeStamp .=" ~ $machine ~ Pid ~ $process]";
	#commnet the below print if no log required...
	print "$TimeStamp $0 Run [$run]...\n" if($debug==1);
	print "$TimeStamp $0 Configuration File Used : $config\n" if($debug==1);
	
	%INFO=();
	&parse_config();
	my $min=&trim(`date +%M`);
	my $hour=&trim(`date +%H`);
	my $date_month=&trim(`date +%d`);
	my $month=&trim(`date +%m`);
	my $day=&trim(`date +%a`);
	$day=&map_day($day);
	
		foreach my $key_job (sort {$a<=>$b} keys %INFO)
		{
		my %RUN=();
		my $c_min =	$INFO{$key_job}{'MIN'};
		my $c_hour=	$INFO{$key_job}{'HOUR'};
		my $c_date_month=$INFO{$key_job}{'DATE_MONTH'};
		my $c_month=$INFO{$key_job}{'MONTH'};
		my $c_day= $INFO{$key_job}{'DAY'};
		my $c_command=$INFO{$key_job}{'COMMAND'};

#		print "$TimeStamp Current Info : MIN:$min, HOUR:$hour, DATE:$date_month, MONTH:$month, DAY:$day\n";
#		print "$TimeStamp Config Final : MIN:$c_min, HOUR:$c_hour, DATE:$c_date_month, MONTH:$c_month, DAY:$c_day, COMMAND:$c_command\n";
		
		#Checking the Run condion of job
		&set_run_flg($c_min,$min,\%RUN);
		&set_run_flg($c_hour,$hour,\%RUN);
		&set_run_flg($c_date_month,$date_month,\%RUN);
		&set_run_flg($c_month,$month,\%RUN);
		&set_run_flg($c_day,$day,\%RUN);
		
			if(defined $RUN{0})
			{
			#skipping the job if run condition is not met
			#commnet the below print if no log required...
			print "$TimeStamp Skipping : $c_command\n" if($debug==1);
			}
			else
			{
			#running the job when run condition is met
			my $child_pid;
				if (!defined($child_pid = fork()))
				{
				die "cannot fork: $!";
				}
				elsif ($child_pid)
				{
				# I'm the parent
				#wait for child job to finish and kills <defunct> process[“zombies”]
				wait;
				}
				else
				{
				# I'm the child
				#commnet the below print if no log required...
				print "$TimeStamp Fork Process Running  : $c_command\n" if($debug==1);
				#runs the job in fork process ans exits once completed.
				system("nohup $c_command &");
				#print "$TimeStamp Done Running : $c_command, exiting fork process...\n";
				exit(0);
				} 
			}
		#print "\n$TimeStamp NEXT JOB.....\n";
		}
	#commnet the below print if no log required...
	print "$TimeStamp sleep 60...\n\n" if($debug==1);
	sleep 60;
	$run++;
	}


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

=head
Checks the config [time] and current [time] and set the flags, whether the job to run or not

=cut

sub set_run_flg
{
my ($config_info,$current_info,$RUN)=@_;

my $flg=0;
my %CONFIG_INP=();
my @CONF=split(/\,/,$config_info);
@CONF= map {trim($_)} @CONF;
@CONF= map {$_=sprintf("%02s",$_);} @CONF;
$current_info=sprintf("%02s",$current_info);

map {$CONFIG_INP{$_}=1} @CONF;
#print Dumper(\%CONFIG_INP);

	if($config_info eq '*')
	{
	$flg=1;
	}
	elsif(defined $CONFIG_INP{$current_info})
	{
	$flg=1;
	}
	else
	{
	$flg=0;
	}

#print "$config_info,$current_info--FLG:::$flg\n";
$$RUN{$flg}=$flg;

return;
}

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

=head
Validates the configuration information.


Minute   Hour   Day of Month       Month          Day of Week        Command    
(0-59)  (0-23)     (1-31)    (1-12 or Jan-Dec)  (0-6 or Sun-Sat)                
0        2          12             *               0,6           /usr/bin/find

=cut

sub check_range
{
my ($config_info,$range,$line)=@_;

my ($lower,$higher)=split(/-/,$range,2);
$lower=sprintf("%02s",$lower);
$higher=sprintf("%02s",$higher);

my %flg=();
my @CONF=split(/\,/,$config_info);
@CONF= map {trim($_)} @CONF;
@CONF= map {$_=sprintf("%02s",$_);} @CONF;

#print "$config_info,$range,$higher,$lower----$line---\n";
	if($config_info eq '*')
	{
	$flg{1}=1;
	}
	elsif($config_info=~ /\,/)
	{
	map { if($_ >= $lower && $_ <=  $higher){ $flg{1}=1; } else { $flg{0}=1; } }@CONF;
	}
	elsif($config_info >= $lower && $config_info <= $higher)
	{
	$flg{1}=1;
	}
	else
	{
	$flg{0}=1;
	}
	
	if(defined $flg{0})
	{
	print "ERROR >> Incorrect Config Info [$config_info], Ex: [$range], $0 Line Number ",__LINE__, "\n";
	print "LINE  >>$line<<\n";
	exit(1);
	}

return;
}

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

#Parse the config File and feeds %INFO hash

sub parse_config
{
	unless (-f $config)
	{
	print "The Cron Own Configuration file NOT Found : $config\n";
	print "Exiting...\n";
	exit(1);
	}

open (RR,$config) || die "Couldn't Open for reading $config : $!\n";
my $count=1;
#print "~~~~~~*****CONFIG PARSE START******~~~~~~\n";
	foreach my $line (<RR>)
	{
	$line=&trim($line);
	next if($line=~/^\#|^$/);
	my $TimeStamp=`date +[%Y-%m-%d' '%H:%M:%S]`;
	chomp $TimeStamp;
	$line=~ s/\s*\,\s*/,/g;
	
	#print "$TimeStamp Config Info : $line\n";
	my ($min,$hr,$date_month,$month,$day_week,$command)=split(/\s+/,$line,6);
	$day_week=&map_day($day_week,$line);
	#print "---$month===\n";
	$month=&map_month($month,$line);
	$min=sprintf("%02s",$min) if ($min ne '*');
	$hr=sprintf("%02s",$hr) if ($hr ne '*');
	$date_month=sprintf("%02s",$date_month) if ($date_month ne '*');
			
	&check_range($min,"0-59",$line);
	&check_range($hr,"0-23",$line);
	&check_range($date_month,"1-31",$line);
	&check_range($month,"1-12",$line);
	&check_range($day_week,"0-6",$line);

	#print "Config Info#:#MIN:$min, HOUR:$hr, DATE:$date_month, MONTH:$month, DAY:$day_week, COMMAND:$command\n";
			
	$INFO{$count}{'MIN'}=$min;
	$INFO{$count}{'HOUR'}=$hr;
	$INFO{$count}{'DATE_MONTH'}=$date_month;
	$INFO{$count}{'MONTH'}=$month;
	$INFO{$count}{'DAY'}=$day_week;
	$INFO{$count}{'COMMAND'}=$command;
	$count++;
	}
#print "~~~~~~~~*****CONFIG PARSE DONE******~~~~~~~~\n\n";
return;
}
#####################################################################
=head
Mapping of day run, returns numaric value or * depending upon the config inputs 
exits if invalid day
=cut
sub map_day
{
my ($day_inp,$line)=@_;

my %MAP=(
	'*' => '*',
	'0' => '0',
	'1' => '1',
	'2' => '2',
	'3' => '3',
	'4' => '4',
	'5' => '5',
	'6' => '6',
	'Sun' => '0',
	'Mon' => '1',
	'Tue' => '2',
	'Wed' => '3',
	'Thu' => '4',
	'Fri' => '5',
	'Sat' => '6',
	'Su' => '0',
	'Mo' => '1',
	'Tu' => '2',
	'We' => '3',
	'Th' => '4',
	'Fr' => '5',
	'Sa' => '6'
);


my @day=();	
	
	if($day_inp=~ /\,/)
	{
	my @DD=split(/\,/,$day_inp);
		foreach my $d (@DD)
		{
		my $dd=ucfirst($d);
		push(@day,$dd);
		} 
	}
	else
	{
	my $dd=ucfirst($day_inp);
	push(@day,$dd);
	}

my $day;

	foreach my $dd (@day)
	{
		if(defined $MAP{$dd})
		{
		$day .="$MAP{$dd},";
		}
		else
		{
		print "ERROR >> Invalid Day [$day_inp] Provided in config File : $config, $0 Line Number ",__LINE__, "\n";
		print "LINE  >>$line<<\n";
		exit(1);
		}
	}

chop $day;

return $day;
}
#####################################################################

=head
mapping of month retuns numeric value or * depending on the config info
exits if invalid month
=cut

sub map_month
{
my ($month_inp,$line)=@_;

#print "--$month_inp--==$line--\n";

my %MAP=(
	'*' => '*',
	'1' => '01',
	'2' => '02',
	'3' => '03',
	'4' => '04',
	'5' => '05',
	'6' => '06',
	'7' => '07',
	'8' => '08',
	'9' => '09',
	'01' => '01',
	'02' => '02',
	'03' => '03',
	'04' => '04',
	'05' => '05',
	'06' => '06',
	'07' => '07',
	'08' => '08',
	'09' => '09',
	'10' => '10',
	'11' => '11',
	'12' => '12',
	'Jan' => '1',
	'Feb' => '2',
	'Mar' => '3',
	'Apr' => '4',
	'May' => '5',
	'Jun' => '6',
	'Jul' => '7',
	'Aug' => '8',
	'Sep' => '9',
	'Oct' => '10',
	'Nov' => '11',
	'Dec' => '12',
	'Ja' => '1',
	'Fe' => '2',
	'Ma' => '3',
	'Ap' => '4',
	'Ma' => '5',
	'Ju' => '6',
	'Ju' => '7',
	'Au' => '8',
	'Se' => '9',
	'Oc' => '10',
	'No' => '11',
	'De' => '12'

);

my $month="";
	
my @month=();	
	
	if($month_inp=~ /\,/)
	{
	my @mon=split(/\,/,$month_inp);
		foreach my $m (@mon)
		{
		my $mon=ucfirst($m);
		push(@month,$mon);
		} 
	}
	else
	{
	my $mon=ucfirst($month_inp);
	push(@month,$mon);
	}

	foreach my $mon (@month)
	{
		if(defined $MAP{$mon})
		{
		$month .="$MAP{$mon},";
		}
		else
		{
		print "ERROR >> Invalid Month [$month_inp] Provided in config File : $config, $0 Line Number ",__LINE__, "\n";
		print "LINE  >>$line<<\n";
		exit(1);
		}
	}
chop $month;
return $month;
}

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

=head 

This Subroutine trims pre-post white Spaces/new line from the string

Arguments:

1. $var (input String)

=cut

sub trim
{
my ($var)=@_;
return unless (defined $var);
$var=~s /^\s+|\s+$//g;
return $var;
}

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

sub usage
{

my $INFO=<<EOD;	

Usgage : $0
  
  Options:
    -h : Help
    -d : Debug Printing Mode
    -c : Config file to be used	(No Space between the arguments) [Also Works with Options [config= | conf=]
         Ex: i.   -c/abc/xyz/mycron.conf 
           	 ii.  conf=/abc/xyz/mycron.conf
             iii. config=/abc/xyz/mycron.conf	  

Execution Method :
    1) Running Script Normally (No debug Printing log)... 
        nohup cron_own.pl &

    2) Running Debug Printing Mode (Prints log)... 
        nohup cron_own.pl -d >>! \$HOME/cron_own.log &

	3) Running Script Normally using diffrent config file (No debug Printing log)... 
        nohup cron_own.pl config=/abc/xyz/mycron.conf &

	
***Configuration File Name : $config***

Note : If Configuration File is Not Available, then Create One with Above name.  

##################################################################################
####                                                                          #### 
## Configuration Parameter Description :                                        ##
##                                                                              ##
## Minute   Hour   Day of Month       Month          Day of Week        Command ##    
## (0-59)  (0-23)     (1-31)    (1-12 or Jan-Dec)  (0-6 or Sun-Sat)             ##                 
##                                                                              ##
## **************************************************************************** ##
## Example : (Run in every 00 min of an Hour)                                   ## 
##                                                                              ##
## 00 * * * * /abspath/script_name >> /abspathlog/logname 2>&1                  ##
####                                                                          ####
##################################################################################
EOD

print "$INFO\n";
return;
}
