#!/usr/bin/perl

use strict;
use warnings;
use Time::Local;

my $fileList = "";
my $fileSystem = "";
my $outputFile = "";
my $scriptName = $0;
my $opt = "";
my $debug = 0;

my $serverMtime = 0;
my $serverCtime = 0;
my $clientMtime = 0;
my $clientCtime = 0;
my $clientInode = 0;
my $serverInode = 0;

#
# Function readCommandLine
# simple command line parser
#
sub readCommandLine
{
   if (@ARGV < 3) 
   {
      die "Usage: $scriptName -filelist=<filelist> -filesystem=<filesystem> -outfile=<output filename> [-debug=<0|1|2|3>]\n";
   }
   
   foreach $opt(@ARGV)
   {
      if($opt =~ m/-filelist=(.*)/)
      {
         $opt =~ s/-filelist=//g;
         $fileList = $opt;
      }      
      elsif($opt =~ m/-filesystem=(.*)/)
      {
         $opt =~ s/-filesystem=//g;
         $fileSystem = $opt;
      }      
      elsif($opt =~ m/-outfile=(.*)/)
      {
         $opt =~ s/-outfile=//g;
         $outputFile = $opt;
      }      
      elsif($opt =~ m/-debug/)
      {
         $opt =~ s/-debug=//g;
         $debug = $opt;
      }
   }
   
   print "$scriptName: Arguments: filelist($fileList) filesystem($fileSystem) outfile($outputFile) debug($debug)\n";
   return;
}

#
# Function extractServerTime
# Convert the time stamps returned for mtime and ctime from the server into seconds.
# This allows to compare it with the local times
#
sub extractServerTime
{
   my ($fileName, @line) = @_;
   my $mTime = "$line[1] $line[2]";
   my $cTime = "$line[8] $line[9]";
   my ($mon, $mday, $year, $hour, $min, $sec);
   
   ($mon, $mday, $year, $hour, $min, $sec) = split(/[\s:\/]+/, $mTime);
   $serverMtime = timelocal($sec, $min, $hour, $mday, $mon-1, $year);
   ($mon, $mday, $year, $hour, $min, $sec) = split(/[\s:\/]+/, $cTime);
   $serverCtime = timelocal($sec, $min, $hour, $mday, $mon-1, $year);
   
   print "$scriptName: Server times for file ($fileName): mtime($serverMtime) ctime($serverCtime)\n" if ($debug > 2);
   return;
}

#
# Function extractServerInode
# Extract the server inode number from the received query
#
sub extractServerInode
{
   my ($fileName, @line) = @_;
   $serverInode = $line[11];
   $serverInode =~ s/\n//g;
   
   print "$scriptName: Server inode for file ($fileName): inode($serverInode)\n" if ($debug > 2);
   return;
}

#
# Function getStatInformation
# Perform a stat() call on the file name that was received from the server query
#
sub getStatInformation
{
   my ($fileName) = @_;
   my @statArray = stat($fileName);
   
   $clientInode = $statArray[1];
   $clientMtime = $statArray[9];
   $clientCtime = $statArray[10];
   
   print "$scriptName: Client times for file ($fileName): mtime($clientMtime) ctime($clientCtime)\n" if ($debug > 2);
   print "$scriptName: Client inode for file ($fileName): inode($clientInode)\n" if ($debug > 2);
   return;
}

#
# Function writeCandidateToFile
# simple file IO
#
sub writeCandidateToFile
{
   my ($fileName) = @_;
   
   open(FILE, ">>", "$outputFile") or die "Can't open $outputFile: $!\n";
   print FILE "$fileName\n";
   close(FILE);
   
   print "$scriptName: Recall candidate written to output file ($fileName)\n" if ($debug > 0);
   return;
}

#
# Function queryServer
# Performs a query against the IBM Spectrum Protect server and receives a list of all
# protected file for a given file system. Based on the received list the function 
# analyses whether the file is a candidate for the next backup or not.
# If the file is a candidate add it to a list of files to be recalled.
#
sub queryServer
{
   my $command = "/usr/bin/dsmc query backup -detail -filelist=$fileList";
   print "$scriptName: Execute command ($command)\n" if ($debug > 0);
   open(COMMAND, "$command |") or die "Can't open $command: $!\n";
   
   my @line = "";
   my @path = "";
   my $fileName = "";
   my $foundNewFile = 0;
   my $foundChangedFile = 0;
   my $extractTimeForFile = 0;
   my $extractInodeForFile = 0;
   my $numberOfFiles = 0;
   
   while(<COMMAND>)
   {
      # The following output line was found:
      # ANS1345E No objects on the server match '<filename>'
      # This is a file that is migrated, but was renamed and is a candidate for backup
      # The file must be recalled. Add it to the recall list
      
      if($_ =~ m/^(.*)ANS1345E(.*)/)
      {
         @line = split(/ /, $_);
         @path = split(/\'/, $line[7]);
         $fileName = $path[1];
         $fileName =~ s/\n//g;
         $foundNewFile = 1;
         print "$scriptName: Found new file ($fileName)\n" if ($debug > 1);
      }
      
      # The following output line was found:
      # 1,048,576  B  09/26/16   16:13:08             DEFAULT              A  <filename>
      # It can be the case that migrated files were renamed in round robin. Means:
      # fileC -> fileA. Then fileA would still exist in file system and in the server.
      # In this case the time stamps on the server and in the file system must be different.
      
      elsif($_ =~ m/^(.*)$fileSystem(.*)/)
      {
         @line = split(/ /, $_);
         @line = grep /\S/, @line;
         $fileName = $line[6];
         $fileName =~ s/\n//g;
         $extractTimeForFile = 1;
      }
      
      # The following output line follows to the one that contains the file name:
      # Modified: 09/23/16   11:33:14  Accessed: 09/23/16   11:45:33  Inode changed: 09/23/16   11:33:14
      # The time stamps belong to the file that was found in the previous line. Convert the time stamps
      # and compare it to the time stamps returned from stat() call. If time stamps are different
      # we have a candidate for recall and backup
      
      elsif($extractTimeForFile == 1)
      {
         @line = split(/ /, $_);
         @line = grep /\S/, @line;
         $extractTimeForFile = 0;
         &extractServerTime($fileName, @line);
         &getStatInformation($fileName);
         
         if( $serverMtime != $clientMtime ||
             $serverCtime != $clientCtime )
         {
            $foundChangedFile = 1;
            print "$scriptName: Found file with different time stamp ($fileName)\n" if ($debug > 1);
         }
         else 
         {
            # the time stamps were equal. Check if the inode is equal as well.
            # If yes, we have the same file on server and in the file system.
            $extractInodeForFile = 1;
         }
      }
      
      # The following output line follows to the one that contains the file name:
      # Compression Type: None  Encryption Type:        None  Client-deduplicated: NO  Migrated: YES  Inode#: 48413
      # Extract the inode of the file and copare it to the inode of the file in the file system
      
      elsif($extractInodeForFile == 1)
      {
         @line = split(/ /, $_);
         @line = grep /\S/, @line;
         $extractInodeForFile = 0;
         &extractServerInode($fileName, @line);
         
         if($clientInode != $serverInode)
         {
            $foundChangedFile = 1;
            print "$scriptName: Found file with different inode ($fileName)\n" if ($debug > 1);
         }
      }
      
      # we found have a migrated file in the list that has no backup copy. This must be recalled for backup.
      # Or we found a file with the same name that has a backup copy, but different inode or time stamps.
      # This must be recalled as well for backup.
      
      if($foundNewFile == 1 || $foundChangedFile == 1)
      {
         &writeCandidateToFile($fileName);
         $foundNewFile = 0;
         $foundChangedFile = 0;
         $numberOfFiles++;
      }
   }
   
   close(COMMAND);
   print "$scriptName: Recall of $numberOfFiles files required. List of files written to ($outputFile).\n";
   return;
}

#
# main routine
#
sub main
{
   &readCommandLine;
   &queryServer;
   return;
}

&main;


