#!/usr/bin/perl

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

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

# Set signal handler
$SIG{TERM} = \&signal_handler;
$SIG{QUIT} = \&signal_handler;

my $StoreLogHandler = undef;
my $StoreLocation;        # Default - ""
my $ArchiveLocation;      # Default - ""
my $HotBackupLocation;    # Default - ""
my $ArchiveEnabled;       # Default - "no";
my $HotBackupEnabled;     # Default - "no";
my $CircularLogging;      # Default - "yes";
my $HotBackupLocationTmp;# Default - ""

my $ArchiveInterval;      # Default - 120 seconds 

my $FirstTime_HotBackup = 0;

my $CWD = `pwd`; chomp $CWD;

my $OSNAME = `uname -s`;

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

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

my $signalReceived = 0;
my $PidFile = "$CWD/config/pidfile.store";

my $DEBUG = ( exists( $ENV{DEBUG} ) ? $ENV{DEBUG} : 0 );

###
###
###

sub csstoredExit {
  my $ret_code       = shift;
  unlink($PidFile);
  exit ($ret_code);
}

sub openLog {
  my $filePath       = shift;
  my $fileName       = shift;
  my $logHandlerRef  = shift;
  my $errCode        = 0;
  my $returnValue    = 0;

  chomp( $filePath );
  chop( $filePath ) if ( $filePath =~ /.*\/$/ );
  if ( ! -d $filePath ) {
    $errCode = createDir( $filePath );
    if ( $errCode ) {
      print "ERROR: Invalid Log path in your configuration. Could not create [$filePath].\n";
      $filePath = `pwd`;
      chomp( $filePath );
      print "  NOTICE: Using [$filePath] to store log files.\n";
    }
  }
  if ( open( LOG,  ">>$filePath/$fileName" ) ) {
    print "  NOTICE: Successfully opened log [$filePath/$fileName].\n" if ($DEBUG); 
    print LOG "Starting store service\n";
    $$logHandlerRef = *LOG;
    #print "LOG [$LOG]\n";
  } else {
    print "ERROR: Could not open log [$filePath/$fileName].\n";
    $returnValue = 1;
  }
  return $returnValue;
}

sub closeLog {
  my $logHanlderRef = shift;
  close( $$logHanlderRef ) if ( defined( $$logHandlerRef ) );
}

sub myTee {
  my $message    = shift;
  ### Using $StoreLogHandle global variable
  ### I know, it's dirty, but that's life...
  ### You'll see more of that in the rest of the program ;)
  my $ts = gen_datetimestamp();
  print $StoreLogHandler "$ts - $message" and select((select($StoreLogHandler), $| = 1)[0]) if ( defined( $StoreLogHandler ) );
# Commented out to avoid calls... make it as optional
# print $message and $|=1;
}

sub myPrint {
  my $dbgLevel = shift;
  my $string   = shift;
  myTee $string if $DEBUG > $dbgLevel;
}

my %diskInfos;

###
##  Retrieves Disk Information From the system and consequently populates a hashtable
### second argument can be one of:
###   - filesystem : device the filesystem is mounted on
###   - capacity   : total partition capacity
###   - used       : used space in KB
###   - available  : available disk space in KB
###   - percent    : percentage of used disk space
sub getDiskInfos {
  my $directory = shift;
  my $lookupKey = shift;
  my $forced    = shift;
  
  if ( !exists($diskInfos{$directory}) or defined( $forced ) ) {
    my %newEntry = ( 'capacity' => 0, 'used' => 0, 'available' => 0, 'percent' => 0 );
    myPrint 1,"getting fresh information for [$directory]\n";
    if ( -d $directory ) {
      my $df_result = `df -k $directory|tail -1|sed 's/  */ /g'|cut -f1,2,3,4,5 -d' '`;
    ( $newEntry{filesystem}, $newEntry{capacity} , $newEntry{used}, $newEntry{available}, $newEntry{percent} ) = ( $df_result =~ /(.*?)\s(\d*?)\s(\d*?)\s(\d*?)\s(.*)\%/ );
    }
    $diskInfos{$directory}=\%newEntry;
  }
  return $diskInfos{$directory}->{$lookupKey};
}

###
### Get the actual size that a given directory takes on the disk
###   just performs a 'du' in fact
sub getDirSize {
  my $directory = shift;
  my $forced    = shift;

  if ( ! exists( $diskInfos{$directory} ) or ! exists( $diskInfos{$directory}->{size} ) or defined( $forced ) ) {
    my $size = 0;
    if ( -d $directory ) {
      $size = `du -ks $directory|cut -f1`;
      chomp $size;
    }
    $diskInfos{$directory}->{size}  = $size;
  }

  return $diskInfos{$directory}->{size};
}  

###
### Makes an array with dirnames
sub getSubdirList {
  my $directory = shift;
  my $key       = shift;
  my $filter    = shift;

  $filter = "|grep -v $filter" if ( defined( $filter ) and length( $filter ) );
  if ( -d $directory ) {
    my $list = `ls -Al $directory | grep '^d' $filter| sed 's/  */ /g'|cut -f9 -d' '`;
    myPrint "This is the actual list:\n$list";
    my @array = split( "\n", $list );
    myPrint "Once in the array:\n".join("\n",@array)."=====END===\n";
    $diskInfos{$key} = \@array;
  }
}

sub removeDir {
  my $directory = shift;
  my $returnValue = system( "rm -rf $directory" ) if ( -d $directory );
  return $returnValue; 
}


# We have decided to set a 20% buffer for DB growth after log applied
# and 250MB for storing the logs themselves
sub getRequiredSpace {
  my $csdbSize = shift;
  my $HBpartition = getDiskInfos( $HotBackupLocation, 'filesystem' );
  my $ABpartition = getDiskInfos( $ArchiveLocation, 'filesystem' );
  my $Ratio = ( $HBpartition eq $ABpartition ? 2 : 1 );
  myTee "Hotbackup on [$HotBackupLocation] mounted on [$HBpartition]\n";
  myTee "Archivebackup on [$ArchiveLocation] mounted on [$ABpartition]\n";
  return $Ratio*(($csdbSize*1.2)+250000);
}

sub conditionOK {
  my $directory   = shift;
  my $threshold   = shift;
  my $csdbSize    = shift;
  my $returnValue = 0;      # defaulting the return value to 'false' state
  
  myTee "Checking condition for [$directory], threshold [$threshold], DB [$csdbSize]\n";
  ### Getting the available disk space in $directory partition
  my $freespace = getDiskInfos( $directory, 'available', 'forcibly' ); 
  ### Getting the percentage of disk occupied on $directory partition
  my $percentage= getDiskInfos( $directory, 'percent' ); #Note: no need to 'force' a refresh of the info as we just did it for $freespace
  my $reqSpace  = getRequiredSpace($csdbSize);
 
  ### implemenetation of the algorithm to verify if it meets the requirements
  myPrint 3, " directory       [$directory]\n";
  myPrint 3, " freespace       [$freespace]kB\n";
  myPrint 3, " required        [$reqSpace]kB\n";
  myPrint 3, " This is".($freespace>$reqSpace?" ":" not ")."enough\n";
  myPrint 3, " csdbSize        [$csdbSize]kB\n";
  myPrint 3, " percentage used [$percentage]%\n";
  myPrint 3, " Max threshold   [$threshold]%\n";
  myPrint 3, " Threshold is".($percentage<$threshold?" not ":" ")."reached\n";
  if ( ( $freespace > $reqSpace ) && ( $percentage < $threshold ) ) {
    myPrint 4, "!!!There is enough space on partition\n";
    $returnValue = 1;
  }
  
  return $returnValue;
}

sub createDir {
  my $directory = shift;
  my $returnValue = 0; 
  if ( ! -d $directory ) {
    myTee "Creating directory [$directory]";
    $returnValue = system( "mkdir -p $directory" );
    myTee "... " .( $returnValue ? "error" : "success") . "\n";
  }
  return $returnValue;
}


sub rollOver {
  my $csdbDir     = shift;
  my $destDir     = shift;
  my $min         = shift;
  my $max         = shift;
  my $filter      = shift;
  my $key         = $destDir . '-list';

  ### I know it looks stupid, but it's only a precaution in case $max < $min by mistake
  $max = ( $max > $min ? $max : $min );

  getSubdirList( $destDir , $key, $filter );

  
  my $numDirs = $#{ $diskInfos{$key} } + 1;	
  my $initialNumDirs = $numDirs;
  my $didRollOver = 0;
  myPrint 4,"Testing if among the [$numDirs] directories\n";
  myPrint 4,"present in [$destDir], some have to be removed...\n";
  myPrint 4,"We want at least [$min] back-ups and [$max] at most\n";
  while ( $numDirs >= $max and $numDirs > 0 ) {
    $didRollOver = 1;
    $oldestDir = shift( @{ $diskInfos{$key} } );
    removeDir( "$destDir/$oldestDir" );
    $numDirs = $#{ $diskInfos{$key} } + 1;	
    ### TODO: SEND AN EMAIL TO THE ADMIN TO TELL HIM THIS IS PART OF THE PROCESS ACCORDING TO HIS SETTINGS
  }
  if ( $didRollOver ) {
    myTee "  WARNING: Removing directory [$oldestDir] from [$destDir] according to system settings.\n";
    myTee "           In backup directory [$destDir] we had [$initialNumDirs] days worth of backup\n";
    myTee "           According to the system settings, we must keep between [$min] and [$max] days of backup\n";
    myTee "           We now have [$numDirs] days of backup in [$destDir]\n";
  }
}

#####################################################################################
# Check available disk and Roll Over 
#####################################################################################
sub checkDiskspace {
  my $csdbDir     = shift;
  my $destDir     = shift;
  my $min         = shift;
  my $threshold   = shift;
  my $key         = $destDir . '-list';
  my $returnValue = 1; # Defaulting to 'true' 

  myPrint(3,"Parameters received by checkDiskspaceAndRollover:\n");
  myPrint(3,"  -csdbDir     [$csdbDir]\n");
  myPrint(3,"  -destDir       [$destDir]\n");
  myPrint(3,"  -threshold     [$threshold]\n");

  # space left in partition
  my $freeSpace = getDiskInfos( $destDir, 'available' );
  my $numDirs = $#{ $diskInfos{$key} } + 1;

  ### ????????????????
  ### WHAT IF THERE IS ENOUGH SPACE IN THE BEGINING OF THE PROCESS
  ### BUT ANOTHER APPLICATION BUILDS UP DATA IN THE SAME PARTITION
  ### ????????????????
  ### Is there enough space to backup ???
  while ( ! conditionOK( $destDir, $threshold, $diskInfos{$csdbDir}->{size} ) and ( $numDirs >= $min ) ) {
    myPrint 5, "  Conditions not met\n";
    ### NO, there's not enough space to h!backup
    ### Is there a backup we can get rid of ?
    ### !!! Are we not going to get rid of ALL h!backups ?
    ### Retrieving name of ldest backup directory
    $oldestDir = shift( @{ $diskInfos{$key} } );
    ### GETTING RID OF THE OLDEST BACKUP
    removeDir( "$destDir/$oldestDir" );
    $numDirs = $#{ $diskInfos{$key} } + 1;
    myTee "  WARNING: Removing directory [$oldestDir] because of lack of disk space";
    ### TODO: SEND A MAIL TO NOTIFY ADMIN OF GETTING RID OF THAT DIR
  }

  ### NOW THERE SHOULD BE ENOUGH SPACE TO BACKUP WITHOUT PROBLEMS...
  ### LET'S CHECK IT OUT
  if ( ! conditionOK( $destDir, $threshold, $diskInfos{$csdbDir}->{size} ) and ( $numDirs <= $min ) ) {
    my $reqSpace = getDirSize( $csdbDir );
    $reqSpace = getRequiredSpace( $reqSpace );
    myTee " WARNING: Not enough space in [$destDir]\n";
    myTee "   Space required  [$reqSpace]KB\n";
    myTee "   Space available [$freeSpace]KB\n";
    ### THERE'S DEFINITELY NOT ENOUGH SPACE ON THAT PARTITION
    ### TODO: SEND AN EMAIL TO LET THE ADMIN KNOW HIS SYSTEM SUCKS
    $returnValue = 0;
  }
  return $returnValue;
}

#####################################################################################
# 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;
      myTee "  $key = $value\n";
    }
  } # Run thru' all the lines - only last uncommented entry will be effective 

  if (!defined($retval)) {
    myTee "  NOTICE: Defaulting [$variable] to [$default]\n";
    return $default;
  } else {
    return $retval;
  }
}

sub SendMail {
  `echo "Archive/Hotbackup failed and is not running.\nThis requires immediate attention."  > /tmp/msg.txt`;

  if ( $OSNAME =~ /Linux/i ) {
    `/bin/mail -s "URGENT: Calendar Hotbackup Failed" $alarm_email_address < /tmp/msg.txt`;
  } else {
    `/bin/mailx -s "URGENT: Calendar 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) {
     myTee "VerifyDB: failures detected - $dblocation/$dbfile\n";
     &SendMail();
     return -1;
  } else {
     myTee "VerifyDB: $dblocation/$dbfile is good.\n";
     return 0;
  }
}

sub secondsToMidnight {
  my $time = `/bin/date '+%H%M%S'`;
  my ( $hour, $minute, $second ) = ( $time =~ /(\d{2})(\d{2})(\d{2})/ );
        myPrint 5, "Hours [$hour] min[$minute] sec[$second]\n";
  return ( 86400 - ( ( ( $hour * 60 ) + $minute ) * 60 ) + $second ); # until midnight
}

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

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

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

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

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

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

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

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

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

  `touch $dblocation/verify.complete`;

  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()
  myTee "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
  myTee "Copying database files to $Dest_Dir\n";

  my (@db_files) =  `cd $StoreLocation; ls *.db`;
  my ($num_dbfiles) = scalar @db_files;

  if ($num_dbfiles != 8) {
      myTee "SnapshotDB - Number of db files ($num_dbfiles) is incorrect. Expecting 8. \n";
      $ret_val = 1;
      return $ret_val;
  }

  foreach (@db_files) {
    chomp;
    myTee "Copying database file $_ to $Dest_Dir\n";
    `/bin/dd bs=4096 if=$StoreLocation/$_ of=$Dest_Dir/$_  2>&1 | /bin/grep -i records  > /dev/null`;

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

  `touch $Dest_Dir/copy.complete`;

  $FirstTime_HotBackup = 1;

  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 {
  ### This Loop runs until a signal tells us to exit
  while ( ! $signalReceived ) {

    $dayChanged = 0;
    if ( $HotBackupDynamicallyDisabled and $ArchiveDynamicallyDisabled ) {
      myStatusNotification( "Archivebackup" );
      myStatusNotification( "Hotbackup" );
    }

    $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 ) {
      myTee "Creating hot backup tmp directory for keeping to be deleted log files $HotBackupLocationTmp\n";
      createDir( $HotBackupLocationTmp );
    }
  
    if ( $ArchiveEnabled =~ /yes/i ) {
      if ( ( ! -d $curr_archive_dir ) or ( ! -e "$curr_archive_dir/copy.complete" ) ) {
        ### Rollover
        rollOver( $StoreLocation, $ArchiveLocation, $ArchiveMinDays, $ArchiveMaxDays );
        removeDir( $curr_archive_dir ) if ( -d $curr_archive_dir );
        createDir( $curr_archive_dir );
        ### Check disk space
        if ( checkDiskspace( $StoreLocation, $ArchiveLocation, $ArchiveMinDays, $ArchiveThreshold ) ) {
          # Call archive database files function SnapshotDB()
          $ret_val = &SnapshotDB($curr_archive_dir);
        } else {
          $ret_val = 0;
          # Disabling Archival Backup because of lack of diskspace
          myTee "Notice: Store archiving disabled because of lack of disk space\n";
          $ArchiveEnabled = "No";
          $ArchiveDynamicallyDisabled = 1;
  
          myStatusNotification( "Archivebackup" );
       
        }
   
        if ( $ret_val ) {
          myTee "1 Fatal Error: SnapshotDB failed for archive backup at $curr_archive_dir \n";
          csstoredExit (-1);
        }
      } 
    } else {
      myStatusNotification( "Archivebackup" ) if ( $ArchiveDynamicallyDsiabled );
    }
  
    if ( $HotBackupEnabled =~ /yes/i ) {
      if ( ( ! -d $curr_hotbackup_dir ) or ( ! -e "$curr_hotbackup_dir/verify.complete") ) {
        ### Rollover
        rollOver( $StoreLocation, $HotBackupLocation, $HotBackupMinDays, $HotBackupMaxDays, 'tmp' );
        removeDir( $curr_hotbackup_dir ) if ( -d $curr_hotbackup_dir );
        createDir( $curr_hotbackup_dir );
  
        ### Check disk space
        if ( checkDiskspace( $StoreLocation, $HotBackupLocation, $HotBackupMinDays, $HotBackupThreshold ) ) {
          # Call archive database files function SnapshotDB()
          $ret_val = &SnapshotDB($curr_hotbackup_dir);
        } else {

          $ret_val=0;
          # Disabling HotBackup because of lack of disk space
          myTee "Notice: Hotbackup disabled because of lack of disk space\n";
          $HotBackupEnabled = "No";
          $HotBackupDynamicallyDisabled = 1;
          myStatusNotification( "Hotbackup" );
        }
        if ( $ret_val ) {
          myTee "2 Fatal Error: SnapshotDB failed for hotbackup at $curr_hotbackup_dir \n";
          csstoredExit (-1);
        }
      } 
    } else {
      myStatusNotification( "Hotbackup" ) if ( $HotBackupDynamicallyDisabled );
    }
  
    ## Here, we just to make sure
    ## that if both archive and hot backup 
    ## have been dynamically disabled because
    ## there is no more space on device, then
    ## we'll change the loop sleep time to 24
    ## hours
    if ( $HotBackupDynamicallyDisabled and $ArchiveDynamicallyDisabled ) {
      if ( $extendLoop ) {
        $ArchiveInterval = 86400; #'til da next dae
      } else {
        $extendLoop=1; 
        $ArchiveInterval = secondsToMidnight; # only sleep until midnight
      }
    }

    if ( $ArchiveEnabled =~ /yes/i ) {
      # 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;
    
      myTee "Moving $numfiles txn log files from $StoreLocation\n";
    
      foreach (@processed_txn_files) {
    
        chomp;
        $curr_log_file = "$StoreLocation/$_";
    
        if ( $HotBackupEnabled =~ /yes/i ) {
          myTee "Copying $curr_log_file to $curr_hotbackup_dir\n";
          `cp -f $curr_log_file $curr_hotbackup_dir`;
          # Check for error
          if ( $? ) {
            myTee ("SnapshotDB - Moving txn log files failed\n");
            $ret_val = $?;
            return $ret_val;
          }
        }
    
        myTee "Moving $curr_log_file to $curr_archive_dir\n";
        `mv -f $curr_log_file $curr_archive_dir`;
    
        # Check for error
        if ( $? ) {
          myTee ("SnapshotDB - Moving txn log files failed\n");
          $ret_val = $?;
          return $ret_val;
        }
    
      }
    
      $save_filename = "log.0000000000";
    }

    if ( $HotBackupEnabled =~ /yes/i ) {
  
      if ( ! -d $curr_hotbackup_dir ) {
        myTee "3 Fatal Error: Cannot find $curr_hotbackup_dir\n";
        &SendMail();
        csstoredExit (-1);
      }
  
      myTee "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 = $_;
        myTee "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
      myTee "Running db_recover -c -h $curr_hotbackup_dir\n";
      `$DB_RECOVER -c -h $curr_hotbackup_dir`;
  
      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 ( ! $signalReceived and ! $dayChanged ) {
  
      myTee "\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.
  
        myTee "Completed the backup for date $curr_datestamp\n";
        $dayChanged=1;
        break;
      }
  
      if ( $HotBackupEnabled =~ /yes/i ) {
        if ( ! -d $curr_hotbackup_dir ) {
          myTee "4 Fatal Error: Cannot find $curr_hotbackup_dir\n";
          &SendMail();
          csstoredExit (-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)
        myTee "Moving processed txn log file from $curr_hotbackup_dir to $HotBackupLocationTmp\n";
        `mv $curr_hotbackup_dir/log.* $HotBackupLocationTmp`;
      }
  
      if ( $ArchiveEnabled =~ /yes/i ) {
        myTee "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 ) {
          myTee "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) {
            myTee "WARNING .......  More than two active log files found in the database directory\n";

            # Send a mail notification to the administrator

            $subject = "Critical -  More than two active log files found in the database directory";
            $body  = "";
            $body .= "\n";
            $body .= "Critical -  More than two active log files found in the database directory\n";
            $body .= "    Monitor the database directory [$StoreLocation] and \n";
            $body .= "    if you continue to see more than two active log files for 30 minutes, n";
            $body .= "    you must fix the situation immediately.\n";
            $body .= "Thank you,\n";
            $body .= "Calendar Store Service\n";
            ### SENDING THE EMAIL
            mySendMail( $from, $to, $subject, $body );

            myTee "Running db_archive again\n";
            @processed_txn_files = `$DB_ARCHIVE -h $StoreLocation`;
          }
        } else {
          myTee "Moving $numfiles txn log files from $StoreLocation to  $curr_archive_dir\n";
        }
    
        foreach (@processed_txn_files) {
          chomp;
          $curr_log_file = "$StoreLocation/$_";
          $save_txn_filename = $_;
          myTee "Working on log file $curr_log_file\n";
    
          if ( $HotBackupEnabled =~ /yes/i ) {
            if ( (lc($_)  cmp lc($last_active_log_file)) >= 0 ) {
              myTee "Copying $_ to $curr_hotbackup_dir\n";
              `cp  $curr_log_file $curr_hotbackup_dir`;
            } else {
              myTee "No need to copy  $_ to $curr_hotbackup_dir (Ignore)\n";
            }
          }
    
          myTee "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/i ) {
  
        if ( ! -d $curr_hotbackup_dir ) {
          myTee "5 Fatal Error: Cannot find $curr_hotbackup_dir\n";
          &SendMail();
          csstoredExit (-1);
        }
  
        myTee "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 ) {
              myTee "Copying $_ to $curr_hotbackup_dir\n";
              `cp  $active_log_file $curr_hotbackup_dir`;
            } else {
              myTee "No need to copy  $_ to $curr_hotbackup_dir (Ignore)\n";
            }
          }
          $last_active_log_file = $save_filename;
        }
  
        # Run db_recover -c on $curr_hotbackup_dir
        myTee "Running db_recover -c -h $curr_hotbackup_dir\n";
        `$DB_RECOVER -c -h $curr_hotbackup_dir`;
  
        myTee "Deleting previously processed txn log file from temporary directory.\n";
        `rm -f $HotBackupLocationTmp/log.*`;
  
      } # if ( $HotBackupEnabled == "yes" ...
  
      myTee "Sleeping for $ArchiveInterval\n";
      sleep ($ArchiveInterval);
    }
  }
  unlink($PidFile);
}

sub mySendMail {
  my $from     = shift;
  my $to       = shift;
  my $subject  = shift;
  my $body     = shift;
  my $tmpDir   = '/tmp';
  my $tmpFile  = 'csstored.eml';
  my $priority = 5;

  $emailCont  = <<END_OF_EMAIL;
From: $from
To: $to
Subject: $subject
X-Priority: $priority

$body  
END_OF_EMAIL
  open EML, ">$tmpDir/$tmpFile";
  print EML $emailCont;
  close EML;
  `mail $to < $tmpDir/$tmpFile`;
}

sub myStatusNotification {
  my $type      =  shift;
  my $minDays   = ( $type =~ /hot/i ? $HotBackupMinDays : $ArchiveMinDays );
  my $Location  = ( $type =~ /hot/i ? $HotBackupLocation : $ArchiveLocation );
  my $Threshold = ( $type =~ /hot/i ? $HotBackupThreshold : $ArchiveThreshold );
  ### PREPARING THE EMAIL
  my $reqSpace = getRequiredSpace( $csdbSize );
  $reqSpace *= $minDays ;
  $subject = "Critical - $type failed";
  $body  = "";
  $body .= "\n";
  $body .= "$type\n";
  $body .= " Backup directory [$Location]\n"; 
  $body .= " Filesystem [" . getDiskInfos( $Location, 'filesystem') . "]\n";
  $body .= " Available diskspace [" . getDiskInfos( $Location, 'available' ) . "]KB\n";
  $body .= " Required disk space [$reqSpace]KB\n";
  $body .= " Requested diskspace threshold [$Threshold]%\n";
  $body .= " Current disk consumption [" . getDiskInfos( $Location, 'percent' ) . "]%\n\n";
  $body .= " Requirements not met.\n\n";
  $body .= "Thank you,\n";
  $body .= "Calendar Store Service\n";
  ### SENDING THE EMAIL
  mySendMail( $from, $to, $subject, $body );
}

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

# Check for the configuration file

`rm -f $PidFile`;
open (PIDFILE_FD, ">$PidFile") or
  die "Can't open $PidFile\n";
print PIDFILE_FD "$$ init\n";
close(PIDFILE_FD);

myTee "\n";

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

# Get the configuration values
myTee "Reading configuration file $ServerConf\n";
$StoreLogFileName    = &ReadConfigurationFile ("logfile.store.logname",                "store.log");
$StoreLogDirName     = &ReadConfigurationFile ("logfile.logdir",                       ".");
openLog( $StoreLogDirName, $StoreLogFileName, \$StoreLogHandler );
$StoreLocation       = &ReadConfigurationFile ("caldb.berkeleydb.homedir.path",        "");
$ArchiveLocation     = &ReadConfigurationFile ("caldb.berkeleydb.archive.path",        "");
$HotBackupLocation   = &ReadConfigurationFile ("caldb.berkeleydb.hotbackup.path",      ""); 
$ArchiveEnabled      = &ReadConfigurationFile ("caldb.berkeleydb.archive.enable",      "yes");
$HotBackupEnabled    = &ReadConfigurationFile ("caldb.berkeleydb.hotbackup.enable",    "yes");
$HotBackupMinDays    = &ReadConfigurationFile ("caldb.berkeleydb.hotbackup.mindays",   "3");
$HotBackupMaxDays    = &ReadConfigurationFile ("caldb.berkeleydb.hotbackup.maxdays",   "6");
$HotBackupThreshold  = &ReadConfigurationFile ("caldb.berkeleydb.hotbackup.threshold", "70");
$ArchiveMinDays      = &ReadConfigurationFile ("caldb.berkeleydb.archive.mindays",     "3");
$ArchiveMaxDays      = &ReadConfigurationFile ("caldb.berkeleydb.archive.maxdays",     "6");
$ArchiveThreshold    = &ReadConfigurationFile ("caldb.berkeleydb.archive.threshold",   "70");
$CircularLogging     = &ReadConfigurationFile ("caldb.berkeleydb.circularlogging",     "yes");
$ArchiveInterval     = &ReadConfigurationFile ("caldb.berkeleydb.archive.interval",    "120");
$alarm_email_address = &ReadConfigurationFile ("alarm.msgalarmnoticercpt",      "root\@localhost");
$service_ready_time  = &ReadConfigurationFile ("service.admin.sleeptime",    "2");
myTee "Reading configuration file - Done\n\n";
$from     = "Calendar Store Service";
$to       = $alarm_email_address;
$csdbSize = getDirSize( $StoreLocation );

# Set process state
open (PIDFILE_FD, ">$PidFile") or
  die "Can't open $PidFile\n";
print PIDFILE_FD "$$ ready\n";
close(PIDFILE_FD);

# Sleep for so many seconds so that the start-cal would report the ready status
sleep ($service_ready_time);

if ((!length($ArchiveLocation)) or (!length($HotBackupLocation)) or
($CircularLogging =~ /yes/i) or (!$alarm_email_address)) {
  while ( ! $signalReceived ) {
  
    my $subject = "Critical - Store Service Not Properly Configured";
    my $body = "***** Action Required *****\n";
    if ( ! length( $ArchiveLocation ) ) {
      my $reqSpace = getRequiredSpace( $csdbSize );
      $reqSpace *= $ArchiveMinDays;
      $body .= <<END_OF_BODY;
Set caldb.berkeleydb.archive.path.
Your current DB size is [$csdbSize]KB.
[$reqSpace]KB of free disk space required.
END_OF_BODY
    }
  
    if ( ! length( $HotBackupLocation ) ) {
      my $reqSpace = getRequiredSpace( $csdbSize );
      $reqSpace *= $HotBackupMinDays;
      $body .= <<END_OF_BODY;
Set caldb.berkeleydb.hotbackup.path.
Your current DB size is [$csdbSize]KB.
[$reqSpace]KB of free disk space required.
END_OF_BODY
    } 

    if ($CircularLogging =~ /yes/i) {
      myTee "ERROR: caldb.berkeleydb.circularlogging enabled.\n";
      $body .= <<END_OF_BODY;
Set caldb.berkeleydb.circularlogging to no.
END_OF_BODY
    }
      
    if (!$alarm_email_address) {
      myTee "ERROR: Invalid admin e-mail address.\n";
      $body .= <<END_OF_BODY;
Set alarm.msgalarmnoticercpt.
END_OF_BODY
    }

$body .= <<END_OF_BODY;
***************************

***** Mandatory ics.conf parameters *****
caldb.berkeleydb.archive.path = no default
caldb.berkeleydb.hotbackup.path = no default
caldb.berkeleydb.archive.enable = "yes or no"
caldb.berkeleydb.hotbackup.enable = "yes or no"
caldb.berkeleydb.circularlogging = "no"
alarm.msgalarmnoticercpt = admin's e-mail address
*****************************************
END_OF_BODY

    myTee "Invalid configuration, notifying $to.\n";
    mySendMail( $from, $to, $subject, $body ); 
    myTee "ERROR: Store Service is not properly configured.\n";
    my $untilMidnight = secondsToMidnight;
    sleep $untilMidnight; # Sleep until midnight
  }
  csstoredExit(0);
}

if ( $ArchiveEnabled =~ /yes/i || $ArchiveEnabled =~ /y/i ) {
  myTee "Notice: Store Archiving is Enabled\n";
} else {
  myTee "Notice: Store Archiving is not Enabled\n";
  `echo "Notice: Archive Backup is not Enabled.\nThis requires immediate attention."  > /tmp/msg.txt`;
  if ( $OSNAME =~ /Linux/i ) {
    `/bin/mail -s "URGENT: Archive Backup is not Enabled" $alarm_email_address < /tmp/msg.txt`;
  } else {
    `/bin/mailx -s "URGENT: Archive Backup is not Enabled" $alarm_email_address < /tmp/msg.txt`;
  }
}

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

  if ( !$HotBackupLocation ) {
    myTee "Error: Invalid hotbackup path\n";
    &SendMail();
    csstoredExit (-1);
  }
} else {
  myTee "Notice: Hot Backup is not Enabled\n";
  `echo "Notice: Hot Backup is not Enabled.\nThis requires immediate attention."  > /tmp/msg.txt`;
  if ( $OSNAME =~ /Linux/i ) {
    `/bin/mail -s "URGENT: Hot Backup is not Enabled" $alarm_email_address < /tmp/msg.txt`;
  } else {
    `/bin/mailx -s "URGENT: Hot Backup is not Enabled" $alarm_email_address < /tmp/msg.txt`;
  }
}

if (!$alarm_email_address) {
  myTee "ERROR: Invalid email address\n";
  &SendMail();
  csstoredExit (-1);
}

&BackupDispatchLoop();

closeLog( \$StoreLogHandler );
csstoredExit(0);

#####################################################################################
# Signal handler
#####################################################################################
sub signal_handler {
  my $signame = shift;
  myTee "Signal SIG[$signame] received ... exiting ...\n";
  $signalReceived++;
}

