#!/usr/bin/perl -w
#
# Copyright (c) 2006 Zmanda Inc.  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 version 2 as published
# by the Free Software Foundation.
#
# 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.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
#
# Contact information: Zmanda Inc, 505 N Mathlida Ave, Suite 120
# Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
#
#

use warnings;
use Getopt::Long;
use Time::Local;
use File::Copy;
use File::Temp qw/ :mktemp  /;
use Fcntl ':flock';
use lib "/usr/local/lib/mysql-zrm";
use ZRM::Common;



my $oldPATH = $ENV{'PATH'};
$ENV{'PATH'} = "/usr/local/bin:/opt/csw/bin:/usr/bin:/usr/sbin:/sbin:/bin:/usr/ucb";

my $zrm_scheduler="/usr/local/bin/mysql-zrm-scheduler";
my $prog="zrm-pre-scheduler";
my $logdir="/var/log/mysql-zrm";
my $logfile="$logdir/$prog.log";
my $mycrontab_fh;
my $mycrontab;


my @PRESCHEDULEROPT = qw/html-report-directory=s
                         pre-scheduler=s
                         action=s
                         backup-level=i
                         interval=s/;

my $MYSQL_ZRM_BASEDIR="/etc/mysql-zrm/";

my $MYSQL_ZRM_CONFIG_FILE=$MYSQL_ZRM_BASEDIR . "mysql-zrm.conf";

sub mprint {
    my $d=localtime();
    &lockLog();
    print LOG "$d: ";
    print LOG @_;
    &unlockLog();
}

sub log_and_die {
    my $d=localtime();
    &lockLog();
    print LOG "$d: ";
    print LOG  @_;
    &unlockLog();
    die @_;
}

# return tomorrow's day of month
sub next_mday() {
    my $now = time;
    my $tomorrow = (localtime($now + 86400))[3];
    return ( $tomorrow );
}


# reschedule crontab, add --delay to differentiate from
# the original crontab
# check if delay > 24, if so
#   skip if it's daily
#   call mysql-zrm now anyway if it's weekly or monthly
#   since we don't want to miss a weekly/monthly backup
# else
#   +n which ( n <= 11) to delay and install new crontab
# when called with delete, just delete the crontab with --delay
sub reschedule()
  {
    my $myaction  = $_[0]; # to edit or delete
    my $later   = $_[1];
    my $setname = $_[2];
    my $int     = $_[3]; #daily, weekly or monthly backup
    my $lev     = $_[4];
    my $found=0;

    ($mycrontab_fh, $mycrontab) = mkstemp( "/tmp/zrm-preschedulerXXXXXXXXXX" );
    $mycrontab_fh=$mycrontab_fh; # quiet warnings
    system ("crontab -l > $mycrontab 2> /dev/null");
    $exit_value  = $? >> 8;
    if ( $exit_value > 1 ) {	# 0 is fine
      # suse crontab returns 1 for "no crontab found"
      &mprint ("ERROR: crontab -l failed.\n");
      &mprint ("Make sure you are allowed to run crontab on this machine\n");
      unlink $mycrontab;
      return 1;
    }
    open (INF, "$mycrontab") || &log_and_die ("ERROR: Cannot open $mycrontab : $!\n");
    open (OUTF, ">$mycrontab.tmp") || &log_and_die ("ERROR: Cannot create $mycrontab.tmp : $!\n");
    $pattern="\-\-action backup \-\-backup\-set $setname.*\-\-interval $int \-\-backup\-level $lev";

    if ( $myaction eq "edit" ) {
      while ( <INF> ) {
	# $1=min, $2=hr, $3=day_month, $4=month, $5=day_wk, $6=dontcare, $7=--delay, $8=delay_num
	if ( /^([^#].*)\s+([0-9]{1,2})\s+([0-9\*]{1,2})\s+([0-9\*]{1,2})\s+([0-9\*])\s+(.*$pattern\s+)(\-\-delay)\s+([0-9]{1,2})$/ ) {
	  $new_delay = $8 + $later;
	  $new_hr    = $2 + $later;
	  if ( $new_hr > 23 ) {
	    $new_hr -= 23;
	    if ( $int eq "weekly") {
	    $new_day_wk  = ($5 + 1)  % 7;
	    $new_day_month = $3;
	    }
	    if ( $int eq "monthly" ) {
	    $new_day_month = &next_mday();
	    $new_day_wk = $5;
		}
	  } else {
	    $new_day_wk = $5;
	    $new_day_month = $3;
	  }
	  $found++;
	  print OUTF "$1 $new_hr $new_day_month $4 $new_day_wk $6 --delay $new_delay\n";
	} elsif ( ! (/^# DO NOT EDIT.*/ || /^# \(\/tmp\/.*crontab.*/ || /^# \(\/tmp\/zrm-prescheduler.*/ || /^# \(Cron version .*/) ) {
	  print OUTF;
	}
      }				#end of while
      if ( $found == 0 ) {    #--delay crontab not found, add one here
	($dontcare,$min,$hr,$mday,$dontcare,$dontcare,$wday,$dontcare) = localtime();
	$hr += $later;
	if ( $hr > 23 ) {
	    $hr -= 23;
	    if ($int eq "monthly") {
		$mday = &next_mday();
	    } else {
		$mday = "*";
	    }
	    if ($int eq "weekly") {
		$wday++, $wday %= 7;
	    } else {
		$wday = "*";
	    }
	} else {
	    if ( $int eq "weekly" ) {
		$mday = "*";
	    } elsif ( $int eq "monthly" ) {
		$wday = "*";
	    }
	}
	if ( $int eq "daily" ) {
	    $mday = $wday = "*";
	}
	print OUTF "$min $hr $mday * $wday /usr/bin/$prog ";
	print OUTF "--action $myaction ";
        print OUTF "--interval $int " if ( $int );
	print OUTF "--backup-level $lev " if ( $lev );
	print OUTF "--delay $later\n";
      }
      &mprint("$setname crontab delayed by $later hour\n");
    }				#end of edit
    elsif ( $myaction eq "delete" ) {
      while ( <INF> ) {
	unless ( /^[^#].*$pattern.*\-\-delay\s+[0-9]{1,2}/ ||
		 /^# DO NOT EDIT.*/ || /^# \(\/tmp\/.*crontab.*/ || /^# \(\/tmp\/zrm-prescheduler.*/ || /^# \(Cron version .*/) {
	  print OUTF;
	}
      }
      &mprint("$setname crontab with delay deleted\n");
    }				#end of delete
    close OUTF;
    close INF;
    move ("$mycrontab.tmp", "$mycrontab") || &log_and_die ("ERROR: move failed: $!\n");

    # all is fine, install the new crontab
    system ("crontab $mycrontab");
    $exit_value  = $? >> 8;
    if ( $exit_value !=0 ) {
      &mprint ("ERROR: crontab -l failed.\n");
      &mprint ("Make sure you are allowed to run crontab on this machine\n");
      unlink $mycrontab;
      return 1;
    } else {
      unlink $mycrontab;
      &mprint ("$setname crontab updated successfully\n");
    }
    return 0;
  }


sub call_reporter ()
  {
    my $name = $_[0];
    my $date=`date +%Y%m%d%H%M%S`;
    chomp($date);
    my $report_out="$name.$date.html";  # report name is $backupsetname.timestamp.html
    my $reporter="/usr/local/bin/mysql-zrm-reporter";

   # generate html output
    if ( $inputs{'html-report-directory'} && $inputs{'html-report-directory'} ne "" ) {
      system "$reporter", "--where", "backup-set=$name", "--latest", "--type", "html", "--output", "$report_out";
    }
  }



#main

if (-t STDIN && -t STDOUT) {
die ("ERROR: cannot start $prog from command line\n");
}

$action = "pre-schedule";
$USAGE_STRING = "";
&initCommon(@PRESCHEDULEROPT);

unless ( -d $logdir ) {
  mkdir ($logdir, 0750) ||
    die ("ERROR: mkdir $logdir failed: $!\n");
}

open (LOG, ">>$logfile") || &log_and_die ("ERROR: Cannot create/open logfile: $!\n");
print STDOUT "Logging to $logfile\n";


my $ret;
my $now="--now";
my $no_plugin=0;
my $exit_value=0;
my $skip=0;
my $level=0;
my $delay=0;


my $interval    = $inputs{"interval"};
my $myaction    = $inputs{"action"};

if ( $inputs{"delay"} ) {
   $delay = $inputs{"delay"};
}

if ( $inputs{"backup-level"} ) {
   $level = $inputs{"backup-level"};
}


if ( $inputs{'pre-scheduler'} && $inputs{'pre-scheduler'} ne "" ) {
    $plugin_app=$inputs{'pre-scheduler'};
    &mprint("pre-scheduler plugin is $plugin_app\n");
} else {
    $no_plugin=1;
    &mprint("no pre-scheduler plugin specified in $MYSQL_ZRM_CONFIG_FILE\n");
}

# call plugin which should return:
#      0    = go ahead call mysql-zrm now
#      1-11 = delay by N hour
#      > 11   = skip this run
if ( $no_plugin == 0 ) {
    system($plugin_app, "--backup-set", $backupset);
    $exit_value  = $? >> 8;
}

if ( $exit_value > 11 ) {
  $skip = 1;;
}

if ( $skip ||
     (($interval eq "daily") && ($delay + $exit_value) > 23 )) { # skip the run
  $ret = &reschedule("delete", 0, $backupset, $interval, $level);
  if ( $ret != 0 ) {
    &log_and_die("ERROR: delete crontab failed\n");
  }
  &mprint("$backupset $interval backup skipped\n");
  exit 0;
}



if ( $no_plugin == 1 || $exit_value == 0 ||
     (($interval ne "daily") &&  ($delay + $exit_value) > 23 )) {
    # call mysql-zrm-scheduler --now
    system($zrm_scheduler, $now, "--backup-set", $backupset, "--interval", $interval, "--backup-level", $level);
    &mprint("ERROR: $zrm_scheduler failed\n") if ( $? == 1 );
    &call_reporter($backupset);

    # remove the delay crontab
    $ret = &reschedule("delete", 0, $backupset, $interval, $level);
    if ( $ret != 0 ) {
	&mprint("ERROR: delete crontab failed\n");
    }
} elsif ( $no_plugin == 0 ) {
    # reschedule crontab with N hrs delay
    $ret = &reschedule("edit", $exit_value, $backupset, $interval, $level);
    if ( $ret != 0 ) {
	&mprint("ERROR: edit crontab failed\n");
    }
}
