#!/usr/bin/perl

#####################################################################################
#  About this tool and Usage
#  
#  
#####################################################################################

#####################################################################################
#  Global Variables 
#####################################################################################

$StoreLocation;        # Default - ""
$ArchiveLocation;      # Default - ""
$HotBackupLocation;    # Default - ""
$ArchiveEnabled;       # Default - "no";
$HotBackupEnabled;     # Default - "no";
$CircularLogging;      # Default - "yes";
$HotBackupLocationTmp;# Default - ""

$ArchiveInterval;      # Default - 120 seconds 

$FirstTime;
$FirstTime_HotBackup = 1;
$FirstTime_Archive = 1;

$CWD = `pwd`; chomp $CWD;

$DB_ARCHIVE    = "$CWD/../tools/unsupported/bin/db_archive";
$DB_RECOVER    = "$CWD/../tools/unsupported/bin/db_recover";
$DB_CHECKPOINT = "$CWD/../tools/unsupported/bin/db_checkpoint";
$DB_STAT       = "$CWD/../tools/unsupported/bin/db_stat";
$DB_VERIFY     = "$CWD/../tools/unsupported/bin/db_verify";

$ServerConf = "$CWD/config/ics.conf";
$ENV{LD_LIBRARY_PATH}="$ENV{LD_LIBRARY_PATH}:../lib";

#####################################################################################
# Generate date time stamp
#####################################################################################

sub gen_datetimestamp {
  my($datetime);
  my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

  # Pad the day and month with a 0
  $mday=sprintf("%02d",$mday);
  $mon=sprintf("%02d",(++$mon));
  $year+=1900;

  # Pad the hour, minutes and with a 0
  $hour=sprintf("%02d",$hour);
  $min=sprintf("%02d",$min);
  $sec=sprintf("%02d",$sec);

  # Create a date time stamp of the format YYYYMMDDHHMMSS
  $datetime=$year.$mon.$mday.$hour.$min.$sec;
 
  return $datetime;
}

#####################################################################################
# Generate date stamp
#####################################################################################

sub gen_datestamp {
  my($datestamp);
  my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

  # Pad the day and month with a 0
  $mday=sprintf("%02d",$mday);
  $mon=sprintf("%02d",(++$mon));
  $year+=1900;

  # Create a date stamp of the format YYYYMMDD
  $datestamp=$year.$mon.$mday;

  return $datestamp;
}

#####################################################################################
#  Read ics.conf file
#      - Read the following attributes
#        caldb.berkeleydb.circularlogging
#        service.admin.checkpoint
#        caldb.berkeleydb.checkpointinterval - Use this as archive interval
#    
#####################################################################################
sub ReadConfigurationFile {
  my($variable, $default) = @_;
  my($retval); 

  open (CONF_FD, "<$ServerConf") or
    &fopen_error("$ServerConf", "ReadConfigurationFile", __FILE__, __LINE__);

  my(@lines) = <CONF_FD>;
  close (CONF_FD);

  foreach (@lines) {
    chomp;
    
    next if (/^!/); # Skip commented lines 

    ($key,$value) = split (/=/, $_);

    if ($key =~ /$variable/i) {  # Caseinsensitive compare 
      $value =~ s/ //g; # Remove spaces
      $value =~ s/"//g; # Remove double-quotes

      $retval = $value;
      print "      $key = $value\n";
    }
  } # Run thru' all the lines - only last uncommented entry will be effective 

  if (!defined($retval)) {
    return $default;
  } else {
    return $retval;
  }
}

sub SendMail {

  `echo "Archive/Hotbackup failed and not running\nThis requires immediate attention\n\n-thanks\narchive/hotbackup"  > /tmp/msg.txt`;

  `/usr/bin/mailx -s "URGENT: Hotbackup Failed" $alarm_email_address < /tmp/msg.txt`;

}

sub VerifyDB {
  my ($dblocation, $dbfile, $special) = @_;

  $tmpfile = "/tmp/csstored.$dbfile.out";
  $tmpdlfile = "/tmp/csstored.dl.out";

  # Clean up the tmp files
  `rm -f $tmpfile`;
  `rm -f $tmpdlfile`;

  # In 3.2.9, deletelog.db requires special processing to ignore 
  # out-of-order message.  So pass $special = 1 for delelelog.db and 
  # 0 for other db files
  if ($special == 0 )  {
    `$DB_VERIFY -o -h $dblocation $dbfile 2>$tmpfile`;
  } else {
    `$DB_VERIFY -o -h $dblocation $dbfile 2>$tmpdlfile`;
    `grep -i -v "Out-of-order key" $tmpdlfile | grep -v "sorted greater than parent entry" | grep -v DB_VERIFY_BAD > $tmpfile`;
  }

  open FD, "<$tmpfile";
  @numlines = <FD>;
  close FD;
  if ($#numlines >= 0) {
     print "VerifyDB: failures detected - $dblocation/$dbfile\n";
     &SendMail();
     return -1;
  } else {
     print "VerifyDB: $dblocation/$dbfile is good.\n";
     return 0;
  }
}

sub VerifyAllDBs {
  my ($dblocation) = @_;
  my ($ret_val) = 0;

  # Do db_verify
  $ret_val = &VerifyDB ($dblocation, "ics50alarms.db", 0);
  if ($ret_val == -1 ) {
    print "VerifyAllDBs: Stopping the csstored.pl process since db_verify failed.\n";
    exit (-1);
  } 

  $ret_val = &VerifyDB ($dblocation, "ics50recurring.db", 0);
  if ($ret_val == -1 ) {
    print "VerifyAllDBs: Stopping the csstored.pl process since db_verify failed.\n";
    exit (-1);
  } 

  $ret_val = &VerifyDB ($dblocation, "ics50journals.db", 0);
  if ($ret_val == -1 ) {
    print "VerifyAllDBs: Stopping the csstored.pl process since db_verify failed.\n";
    exit (-1);
  } 

  $ret_val = &VerifyDB ($dblocation, "ics50gse.db", 0);
  if ($ret_val == -1 ) {
    print "VerifyAllDBs: Stopping the csstored.pl process since db_verify failed.\n";
    exit (-1);
  } 

  $ret_val = &VerifyDB ($dblocation, "ics50calprops.db", 0);
  if ($ret_val == -1 ) {
    print "VerifyAllDBs: Stopping the csstored.pl process since db_verify failed.\n";
    exit (-1);
  } 

  $ret_val = &VerifyDB ($dblocation, "ics50deletelog.db", 1);
  if ($ret_val == -1 ) {
    print "VerifyAllDBs: Stopping the csstored.pl process since db_verify failed.\n";
    exit (-1);
  } 

  $ret_val = &VerifyDB ($dblocation, "ics50todos.db", 0);
  if ($ret_val == -1 ) {
    print "VerifyAllDBs: Stopping the csstored.pl process since db_verify failed.\n";
    exit (-1);
  } 

  $ret_val = &VerifyDB ($dblocation, "ics50events.db", 0);
  if ($ret_val == -1 ) {
    print "VerifyAllDBs: Stopping the csstored.pl process since db_verify failed.\n";
    exit (-1);
  } 

  return $ret_val;

}


#####################################################################################
#  SnapshotDB - 
#      - Create archive directory and hotbackup directory
#      - Run db_checkpoint(), db_stat(), db_archive() 
#        and Copy the database and unprocessed txn log files
#####################################################################################
sub SnapshotDB {
  my ($Dest_Dir) = @_;
  my ($ret_val) = 0;

  # Do db_checkpoint()
  print ("Running db_checkpoint prior to backuping the database files\n");
  `$DB_CHECKPOINT -1 -h $StoreLocation`; `$DB_CHECKPOINT -1 -h $StoreLocation`;

  # Remove the txn log files in the store directory

  # Copy the database files to archive location and hotbackup location
  print ("Copying database files to $Dest_Dir\n");

  `cp $StoreLocation/*.db $Dest_Dir`;

  # Check for error
  if ( $? ) {
    print ("SnapshotDB - Copy failed to $Dest_Dir\n");
    $ret_val = $?;
    return $ret_val;
  }

  # $ret_val = &VerifyAllDBs($StoreLocation);

  return $ret_val;
}

#####################################################################################
#  Verifying Setup after a restart
#      - Get the number of the last log file in the archive backup directory 
#        Make sure that calendar store has all the files since then.
#####################################################################################
sub VerifySetup {
  my ($ret_val) = 0;

  return $ret_val;
}

#####################################################################################
#  Run log archiving and hotbackup creation for failover 
#      - Copy all the log files returned by db_archive utility to backup_archive 
#        and hot_backup directories.
#      - Write the current log files to the database files in hot_backup directory
#      - Remove all the log files returned by db_archive utility except the last one
#      - Run db_verify() on the hot_backup database files
#####################################################################################
sub BackupDispatchLoop {

  $curr_datestamp = &gen_datestamp();

  $curr_archive_dir = "$ArchiveLocation/archive_$curr_datestamp";
  $curr_hotbackup_dir = "$HotBackupLocation/hotbackup_$curr_datestamp";

  # Create HotBack Tmp directory for keeping to be deleted log files
  $HotBackupLocationTmp = "$HotBackupLocation/tmp";

  if ( ! -d $HotBackupLocationTmp ) {
    print ("Creating hot backup tmp directory for keeping to be deleted log files $HotBackupLocationTmp\n");
    `mkdir -p $HotBackupLocationTmp`;
  }

  if ( ! -d $curr_archive_dir ) {
    print ("$datestamp - Creating directories $curr_archive_dir\n");
    `mkdir -p $curr_archive_dir`;
    # Call archive database files function SnapshotDB()
    $ret_val = &SnapshotDB($curr_archive_dir);
 
    if ( $ret_val ) {
      print "Fatal Error: SnapshotDB failed for archive backup at $curr_archive_dir \n";
      exit -1;
    }
  } else {
      #Don't verify here as db_verify may report spurious errors.
      #if ($FirstTime_Archive == 1) {
        # Validate the *.db files
        #$ret_val = &VerifyAllDBs($curr_archive_dir);
        #$FirstTime_Archive = 0;
      #}
  }

  if ( $HotBackupEnabled == "yes" ) {
    if ( ! -d $curr_hotbackup_dir ) {
      print ("$datestamp - Creating directories $curr_hotbackup_dir\n");
      `mkdir -p $curr_hotbackup_dir`;

      # Call archive database files function SnapshotDB()
      $ret_val = &SnapshotDB($curr_hotbackup_dir);
 
      if ( $ret_val ) {
        print "Fatal Error: SnapshotDB failed for hotbackup at $curr_hotbackup_dir \n";
        exit -1;
      }
    } else {
      #Don't verify here as db_verify may report spurious errors.
      #if ($FirstTime_HotBackup == 1) {
        # Run db_recover to correct any spurious corruptions
        #`$DB_RECOVER -c -h $curr_hotbackup_dir`;

        # Validate the *.db files
        #$ret_val = &VerifyAllDBs($curr_hotbackup_dir);
        #$FirstTime_HotBackup = 0;
      #}
    }
  } 

  # Move the initial log files....
  # Run db_archive to get the list of processed txn log files
  $curr_log_file;
  @processed_txn_files = `$DB_ARCHIVE -h $StoreLocation`;
  $numfiles = scalar @processed_txn_files;

  print "Moving $numfiles txn log files from $StoreLocation\n";

  foreach (@processed_txn_files) {

    chomp;
    $curr_log_file = "$StoreLocation/$_";

    if ( $HotBackupEnabled == "yes" ) {
      print "Copying $curr_log_file to $curr_hotbackup_dir\n";
      `cp -f $curr_log_file $curr_hotbackup_dir`;
      # Check for error
      if ( $? ) {
        print ("SnapshotDB - Moving txn log files failed\n");
        $ret_val = $?;
        return $ret_val;
      }
    }

    print "Moving $curr_log_file to $curr_archive_dir\n";
    `mv -f $curr_log_file $curr_archive_dir`;

    # Check for error
    if ( $? ) {
      print ("SnapshotDB - Moving txn log files failed\n");
      $ret_val = $?;
      return $ret_val;
    }

  }

  $save_filename = "log.0000000000";
  if ( $HotBackupEnabled == "yes" ) {

    if ( ! -d $curr_hotbackup_dir ) {
      print "Fatal Error: Cannot find $curr_hotbackup_dir\n";
      exit -1;
    }

    print "Copying active txn log files to $curr_hotbackup_dir\n";

    @active_txn_files = `$DB_ARCHIVE -l -h $StoreLocation`;

    foreach (@active_txn_files) {
      chomp;
      $active_log_file = "$StoreLocation/$_";
      $save_filename = $_;
      print "Copying txn log file $active_log_file to $curr_hotbackup_dir\n";
      `cp  $active_log_file $curr_hotbackup_dir`;
    }
    $last_active_log_file = $save_filename;

    # Run db_recover -c on $curr_hotbackup_dir
    print "Running db_recover -c -h $curr_hotbackup_dir\n";
    `$DB_RECOVER -c -h $curr_hotbackup_dir`;
    #DURAI print "Deleting processed txn log file from $curr_hotbackup_dir\n";
    #DURAI `rm -f $curr_hotbackup_dir/log.*`;

    if ($FirstTime_HotBackup == 1) {
      # Validate the *.db files
      $ret_val = &VerifyAllDBs($curr_hotbackup_dir);
      $FirstTime_HotBackup = 0;
    }

  } # if ( $HotBackupEnabled == "yes" ...

  # Catch the signal, complete the current operation and do a graceful shutdown
  while ( 1 ) {

    print "\n";
    $timestamp = &gen_datetimestamp();
    $datestamp = &gen_datestamp();

    # Time to create the nextday backup
    if ( $datestamp != $curr_datestamp ) {

      #$curr_archive_dir = "$ArchiveLocation/archive_$datestamp";
      #$curr_hotbackup_dir = "$HotBackupLocation/hotbackup_$datestamp";
      #print ("$datestamp - Creating directories $curr_archive_dir and $curr_hotbackup_dir\n");
      #`mkdir -p $curr_archive_dir`;
      #if ( $HotBackupEnabled == "yes" ) {
      #  `mkdir -p $curr_hotbackup_dir`;
      #}
      #$curr_datestamp = $datestamp;

      #Since this utility is expected to be driven by crond and in case of NT by scheduler, 
      #this run will end after it crosses the midnight time.

      print "Completed the backup for date $curr_datestamp\n";
      exit 1;
    }

    if ( $HotBackupEnabled == "yes" ) {
      if ( ! -d $curr_hotbackup_dir ) {
        print "Fatal Error: Cannot find $curr_hotbackup_dir\n";
        exit -1;
      }

      # Move the txn log files for deletion so that we dont delete before the next one is successful
      # db 4.2.52 doesnt like deleting the last log file(s)
      print "Moving processed txn log file from $curr_hotbackup_dir to $curr_hotbackup_dir_delete\n";
      `mv $curr_hotbackup_dir/log.* $HotBackupLocationTmp`;
    }

    print "Store Backup Dispatch starting at $timestamp\n"; 
    # Run db_archive to get the list of processed txn log files
    @processed_txn_files = `$DB_ARCHIVE -h $StoreLocation`;
  
    $numfiles = scalar @processed_txn_files;
    if ( $numfiles == 0 ) {
      print "No processed txn log files found\n";
      $log_files_count = `ls -l $StoreLocation/log.* | wc -l`;
      chomp $log_files_count;

      if ($log_files_count > 1) {
        print "WARNING .......  More than two active log files found in the database directory\n";
        print "Running db_checkpoint\n";
        `$DB_CHECKPOINT -1 -h $StoreLocation`; `$DB_CHECKPOINT -1 -h $StoreLocation`;
        print "Running db_archive again\n";
        @processed_txn_files = `$DB_ARCHIVE -h $StoreLocation`;
      }
    } else {
      print "Moving $numfiles txn log files from $StoreLocation to  $curr_archive_dir\n";
    }

    foreach (@processed_txn_files) {
      chomp;
      $curr_log_file = "$StoreLocation/$_";
      $save_txn_filename = $_;
      print "Working on log file $curr_log_file\n";

      if ( $HotBackupEnabled == "yes" ) {
        if ( (lc($_)  cmp lc($last_active_log_file)) >= 0 ) {
          print "Copying $_ to $curr_hotbackup_dir\n";
          `cp  $curr_log_file $curr_hotbackup_dir`;
        } else {
          print "No need to copy  $_ to $curr_hotbackup_dir (Ignore)\n";
        }
      }

      print "Moving $_ to $curr_archive_dir\n";
      `mv  $curr_log_file $curr_archive_dir`;

    } # foreach (@processed_txn_files ... 
    $last_active_log_file = $save_txn_filename;

    if ( $HotBackupEnabled == "yes" ) {

      if ( ! -d $curr_hotbackup_dir ) {
        print "Fatal Error: Cannot find $curr_hotbackup_dir\n";
        exit -1;
      }

      print "Copying active txn log files to $curr_hotbackup_dir\n";

      @active_txn_files = `$DB_ARCHIVE -l -h $StoreLocation`;

      $active_numfiles = scalar @active_txn_files;
      if ( $active_numfiles > 0 ) {
        foreach (@active_txn_files) {
          chomp;
          $active_log_file = "$StoreLocation/$_";
          $save_filename = $_;
          if ( (lc($_)  cmp lc($last_active_log_file)) >= 0 ) {
            print "Copying $_ to $curr_hotbackup_dir\n";
            `cp  $active_log_file $curr_hotbackup_dir`;
          } else {
            print "No need to copy  $_ to $curr_hotbackup_dir (Ignore)\n";
          }
        }
        $last_active_log_file = $save_filename;
      }

      # Run db_recover -c on $curr_hotbackup_dir
      print "Running db_recover -c -h $curr_hotbackup_dir\n";
      `$DB_RECOVER -c -h $curr_hotbackup_dir`;

      print "Deleting previously processed txn log file from temporary directory.\n";
      `rm -f $HotBackupLocationTmp/log.*`;

    } # if ( $HotBackupEnabled == "yes" ...

    print "Sleeping for $ArchiveInterval\n";
    sleep ($ArchiveInterval);
  }
}

#####################################################################################
#  main program (csstored.pl) - 
#####################################################################################

# Check for the configuration file

print "\n";

if ( ! -f $ServerConf ) {
  die ("Error: Server Configuration file not found - $ServerConf\n");
}

if (!($CWD =~ /sbin$/)) {
  die ("Error: You must run this program from the sbin directory\n\n");
}

# Get the configuration values

print "Reading configuration file $ServerConf\n";
$StoreLocation = &ReadConfigurationFile ("caldb.berkeleydb.homedir.path", "");
$ArchiveLocation = &ReadConfigurationFile ("caldb.berkeleydb.archive.path", "");
$HotBackupLocation = &ReadConfigurationFile ("caldb.berkeleydb.hotbackup.path", ""); 
$ArchiveEnabled = &ReadConfigurationFile ("caldb.berkeleydb.archive.enable", "no");
$HotBackupEnabled = &ReadConfigurationFile ("caldb.berkeleydb.hotbackup.enable", "no");
$CircularLogging = &ReadConfigurationFile ("caldb.berkeleydb.circularlogging", "yes");
$ArchiveInterval = &ReadConfigurationFile ("caldb.berkeleydb.archive.interval", "120");
$alarm_email_address = &ReadConfigurationFile ("service.monitor.emailaddress.to", "");
print "Reading configuration file - Done\n\n";

if ( $CircularLogging =~ /yes/i || $CircularLogging =~ /yes/i ) {
  print ("Error: Cannot run this program because Circular logging is enabled in the server.\n");
  print ("Error: The server will remove the processed log files.\n");
  print ("Error: Disable the circularlogging by setting \"no\" to caldb.berkeleydb.circularlogging \n\n");
  exit -1;
}

# TODO: Make sure that Archive, HotBackup  and Store directories are different.

if ( $ArchiveEnabled =~ /yes/i || $ArchiveEnabled =~ /y/i ) {
  print ("Notice: Store Archiving is Enabled\n");
} else {
  print ("Error: Store Archiving is not Enabled\n");
  exit -1;
}

if ( !$ArchiveLocation ) {
  print ("Error: Invalid archive path\n");
  exit -1;
}

if ( $HotBackupEnabled =~ /yes/i || $HotBackupEnabled =~ /y/i ) {
  print ("Notice: Hot Backup is Enabled\n");

  if ( !$HotBackupLocation ) {
    print ("Error: Invalid hotbackup path\n");
    exit -1;
  }
} else {
  print ("Notice: Hot Backup is not Enabled\n");
}

if ( !$alarm_email_address ) {
  print ("Error: Invalid email address\n");
  exit -1;
}

&BackupDispatchLoop();


1;


