#!/usr/bin/perl
# Copyright 1999-2014. Parallels IP Holdings GmbH. All Rights Reserved.

use strict;
use warnings;

# Searching for Backup/Migration libs

my $agentsShareDir;
my $productRootD;
my $agentSessionPath;

BEGIN {
  sub getProductRootD {
    my $configFile = "/etc/psa/psa.conf";
    if (-f $configFile) {
      my $productRoot;
      open CONFIG_FILE, "$configFile";
      while (<CONFIG_FILE>) {
        next if /^#/;
        if (/^(\S+)\s+(.*?)\s*$/) {
          my ($key, $value) = ($1, $2);
          if ($key eq "PRODUCT_ROOT_D") {
            $productRoot = $value;
            last;
          }
        }
      }
      close CONFIG_FILE;
      return $productRoot;
    }
  }

  $productRootD = getProductRootD();

  if (exists $ENV{'LOCALLIB'}) {
    $agentsShareDir = ".";
    unshift @INC, $agentsShareDir;
  } else {
    if ($productRootD) {
      $agentsShareDir = "$productRootD/PMM/agents/shared";
      push @INC, $agentsShareDir;
      push @INC, "$productRootD/PMM/agents/PleskX";
    }
  }
}

use Getopt::Long;
use Carp qw( confess );

use PleskX;
use Logging;
use Error qw|:try|;
use SpecificConfig;
use XmlNode;
use File::Temp;
use File::Copy;
use File::Path;
use File::Basename;
use Encoding;
use POSIX;
use IPC::Run;
use PmmCli;
use DumpStatus;
use AgentConfig;
use HelpFuncs;

$Error::Debug = 1;

sub usage {
  my $exitcode = shift;

  my $usage = <<'EOF';
Usage: plesk_agent_manager <command> [<options>] <arguments>

Commands:

  server         Backs up whole Plesk.

  resellers-name Backs up selected resellers. Reseller's logins are read from command line,
                 space-separated. If no resellers provided, backs up all resellers
                 on the host.

  resellers-id   Backs up selected resellers. Reseller's identificators are read from command line,
                 space-separated. If no resellers provided, backs up all resellers
                 on the host.

  clients-name   Backs up selected clients. Client's logins are read from command line,
                 space-separated. If no clients provided, backs up all clients
                 on the host.

  clients-id     Backs up selected clients. Client's identificators are read from command line,
                 space-separated. If no clients provided, backs up all clients
                 on the host.

  domains-name   Backs up selected domains. Domain's names are read from command line,
                 space-separated. If no domains provided, backs up all domains
                 on the host.

  domains-id     Backs up selected domains. Domain's identificators are read from command line,
                 space-separated. If no domains provided, backs up all domains
                 on the host.

                 Use Exclude options to exclude some resellers/clients/domains.

  export-dump-as-file
                 Export dump from Local repository to single file
                 Option --dump-file-name provide dump file name to export

  help           Shows this help page

General options:

  -f|--from-file=<file>
                 Read list of domains/clients/resellers from file, not from command line.
                 File should contain list of domains/clients/resellers one per line.

  -v|--verbose
                 Show more information about backup process. Multiple -v
                 options increase verbosity.

  -s|--split[=<size>]
                 Split the generated backups to the parts. Parts are numbered
                 by appending NNN suffixes.

                 Size may be specified in kilobytes (<nn>K), megabytes (<nn>M)
                 and gigabytes (<nn>G). By default in bytes.

                 '-s' option without argument selects default split size:
                 2 gigabytes.

  -z|--no-gzip   Do not compress content files

  -c|--configuration
                 Backup only configuration of objects, not the content.

  --only-mail
                 Backup only mail configuration and content of selected objects.

  --only-hosting
                 Backup only hosting configuration and content of selected objects.

  --suspend
                 Suspend domains during backup operation.

  --skip-logs    Do not save log files in the backup file

  --dump-file-name <dump-file-name>
                 Dump file name to export (with path or relative to dump repository)

  -d|--description=<description>
                 Add description to the dump

FTP options:

 --ftp-login=<ftp_login>
                 Specify the FTP login
 --ftp-password=<ftp_password>
                 Specify the FTP password (used with '--ftp-login')
 --ftp-passive-mode
                 Use FTP passive mode

Internal options:

  --backup-profile-id=<backup-profile-id>
                 Backup profile identificator

  --backup-profile-name=<backup-profile-name>
                 Backup profile name ( default profile name is 'backup' )

  --session-path=<session_path>
                 Session path to store logs and status.

  --migration-mode
                 Run in migration mode.

  --owner-uid=<guid_of_user_who_start_backup>
                 Backup owner unique identificator.

  --owner-type=<type_of_user_who_start_backup>
                 Backup owner type.

  --dump-rotation=<count>
                 Number of backups stored in repository

Exclude options:

  --exclude-reseller=<obj1>,<obj2>,...
                 Exclude resellers from backup list.
                 Reseller's logins are read from command line, comma-separated.
                 If no resellers provided, resellers are not backuped

  --exclude-reseller-file=<file>
                 Exclude resellers listed in file from backup list.
                 File should contain list of reseller's logins one per line.

  --exclude-client=<obj1>,<obj2>,...
                 Exclude clients from backup list.
                 Client's logins are read from command line, comma-separated.
                 If no clients provided, clients are not backuped

  --exclude-client-file=<file>
                 Excludes clients listed in file from backup list.
                 File should contain list of client's logins one per line.

  --exclude-domain=<obj1>,<obj2>,...
                 Exclude domains from backup list.
                 Domain's names are read from command line, comma-separated.
                 If no domains provided, domains are not backuped

  --exclude-domain-file=<file>
                 Exclude domains listed in file from backup list.
                 File should contain list of domain's names one per line.


Output file option:
  --output-file=<output_file>
                 /fullpath/filename - regular file,

  ftp://[<login>[:<password>]@]<server>/<filepath> - storing the backup to ftp server.
  FTP_PASSWORD environment variable can be used for setting password.
  FTP option '--ftp-login' can be used for setting login.
  FTP option '--ftp-password' (with '--ftp-login') can be used for setting password.

                 Used to import dump from repository into the single file.

Backup Node option:
  --backup-node=ftp://[<login>[:<password>]@]<server>/<pathtorepo>

EOF

  if ($exitcode) {
    print STDERR $usage;
  } else {
    print $usage;
  }

  exit $exitcode;
}

sub isOldOption {
  my $s = shift;
  return ($s eq '--all' or $s eq '--clients' or $s eq '--domains');
}

#
# Options parsing compatible with 8.0 pleskbackup style
#

sub parseOutpuFile{
  my( $outputFile, $settings, $ftplogin, $ftppwd, $ftppasv ) = @_;

  if ($outputFile =~ /^ftps?:\/\//) {
    if ($outputFile =~ /^(ftp|ftps):\/\/(?:([^:]*)(?::([^:]*))?@)?([^\/:@]+)(?::([0-9]+))?(?:\/?(.*?)([^\/]*?))$/) {
      my %ftp;

      $ftp{'protocol'} = $1;
      $ftp{'login'} = defined $2 ? $2 : '';
      $ftp{'password'} = defined $3 ? $3 : '';

      $ftp{'password'} = $ftppwd if defined $ftppwd;
      $ftp{'login'} = $ftplogin if defined $ftplogin;

      if ($ftp{'password'} eq '' && defined $ENV{'FTP_PASSWORD'}) {
        $ftp{'password'} = $ENV{'FTP_PASSWORD'};
      }

      if ($ftp{'login'} eq '') {
        $ftp{'login'} = 'anonymous';
        $ftp{'password'} = 'plesk@plesk.com' if ($ftp{'password'} eq '');
      }

      die 'FTP password is not specified' if ($ftp{'password'} eq '');

      $ftp{'server'} = $4;
      $ftp{'port'} = $5;
      $ftp{'path'} = $6;
      $ftp{'file'} = $7;
      $ftp{'passive'} = 1 if $ftppasv;
      $settings->{'ftp'} = \%ftp;
    }
    else {
      die 'Bad FTP file format';
    }
  }
  return;
}


sub parseOptions {
  usage(0) unless @ARGV;
  usage(0) if $ARGV[0] eq "--help";

  my $command = '';
  my $optParser = Getopt::Long::Parser->new(config => ['bundling']);

  my %res;
  $res{'verbose'} = 0;
  $res{'gzip'} = 1;

  my ( $split, $outputfile, $ftplogin, $ftppwd, $ftppasv, $backupnode );
  my ( $objectsFromFileName, $profileId, $profileName, $sessionPath, $owneruid, $ownertype, $dumpfilename, $dumpRotation, $description );
  my ( $excludeResellers, $excludeResellerFile, $excludeClients, $excludeClientFile, $excludeDomains, $excludeDomainFile );
  my ( $noWholeVhost, $noWholeMail );
  my ( $pathToSchema );
  my ( $encryptionKey );

  $command = shift @ARGV;

  if (!$command) {
    die "No command in command line" unless (@ARGV);
  }

  usage(0) if $command eq "help";

  $optParser->getoptions("verbose|v" => sub { $res{'verbose'} += 1 },
                         "configuration|c" => sub { $res{'configuration'} = 1 },
                         "split|s:s" => \$split,
                         "no-gzip|z" => sub { $res{'gzip'} = 0 },
                         "output-file=s" => \$outputfile,

                         "ftp-login:s" => \$ftplogin,
                         "ftp-password:s" => \$ftppwd,
                         "ftp-passive-mode" => sub { $ftppasv = 1 },
                         "from-file|f=s" => \$objectsFromFileName,
                         "only-mail" => sub { $res{'only-mail'} = 1 },
                         "only-hosting" => sub { $res{'only-hosting'} = 1 },
                         #"only-database" => sub { $res{'only-database'} = 1 },
                         "suspend" => sub { $res{'suspend'} = 1 },
                         "dump-rotation=s" => \$dumpRotation,
                         "description|d=s" => \$description,

                         "skip-logs" => sub { $res{'skip-logs'} = 1 },
                         "backup-profile-id=s" => \$profileId,
                         "backup-profile-name=s" => \$profileName,
                         "session-path=s" => \$sessionPath,
                         "owner-uid=s" => \$owneruid,
                         "owner-type=s" => \$ownertype,
                         "dump-file-name:s" => \$dumpfilename,
                         "migration-mode" => sub{ $res{'migration-mode'} = 1 },

                         "exclude-reseller=s" => \$excludeResellers,
                         "exclude-reseller-file=s" => \$excludeResellerFile,
                         "exclude-client=s" => \$excludeClients,
                         "exclude-client-file=s" => \$excludeClientFile,
                         "exclude-domain=s" => \$excludeDomains,
                         "exclude-domain-file=s" => \$excludeDomainFile,

                         "no-whole-vhost" => sub { $noWholeVhost = 1; },
                         "no-whole-mail" =>  sub { $noWholeMail = 1; },
                         "get-size" =>  sub { $res{'get-size'} = 1; },
                         "validate-by-schema=s" => \$pathToSchema,
                         "no-sign" => sub { $res{'no-sign'} = 1; },
                         "backup-node=s" => \$backupnode,
                         );

  $agentSessionPath = $sessionPath;
  my $size;
  if (defined $split) {
    $size = parseSize($split);
    if($size) {
      my $minSplitSize = 1024*1024; # Minimum split size is 1M
      $size = HelpFuncs::Max($size, $minSplitSize);
    }
  }
  my $rlimit = AgentConfig::getRLimitFsize();
  if($rlimit) {
    $size = HelpFuncs::Min($size, 512*$rlimit);
  }
  $res{'split-size'} = $size;

  $res{'dump-rotation'} = $dumpRotation + 0 if $dumpRotation && ($dumpRotation + 0) > 0;

  $res{'description'} = $description if $description;

  $res{'validate-by-schema'} = $pathToSchema if $pathToSchema;

  die " '--ftp-password' option should be used with '--ftp-login' option!" if defined $ftppwd and not $ftplogin;
  die "Use only one of the options: 'only-mail', 'only-hosting'" if exists $res{'only-mail'} && exists $res{'only-hosting'};

  if( defined $outputfile ){
     $res{'output-file'} = $outputfile;
     parseOutpuFile( $outputfile, \%res, $ftplogin, $ftppwd, $ftppasv );
  }

  if( defined $backupnode ) {
     $res{'backup-node'} = $backupnode;
     $res{'passive'} = $ftppasv;
     $res{'ftp-password'} = ($ftppwd eq '' && defined $ENV{'FTP_PASSWORD'}) ? $ENV{'FTP_PASSWORD'} : $ftppwd;
  }

  die "'owner-type' option should be used with '--owner-uid' option!" if (defined $ownertype) && ( !defined $owneruid);

  $res{'profile-id'} = $profileId if defined $profileId;
  $res{'profile-name'} = $profileName if defined $profileName;
  $res{'session-path'} = $sessionPath if defined $sessionPath;
  $res{'owner-uid'} = $owneruid if defined $owneruid;
  $res{'owner-type'} = $ownertype if defined $ownertype;

  $res{'no-whole-vhost'} = 1 if $noWholeVhost;
  $res{'no-whole-mail'} = 1 if $noWholeMail;

  $res{'exclude-reseller'} = [split(/,/, $excludeResellers)]  if defined $excludeResellers;
  if ($excludeResellerFile) {
      my @objects = readObjects($excludeResellerFile);
      push @{$res{'exclude-reseller'}}, @objects if scalar(@objects)>0;
   }

  $res{'exclude-client'} = [split(/,/, $excludeClients)]  if defined $excludeClients;
  if ($excludeClientFile) {
      my @objects = readObjects($excludeClientFile);
      push @{$res{'exclude-client'}}, @objects if scalar(@objects)>0;
   }

  $res{'exclude-domain'} = [split(/,/, $excludeDomains)]  if defined $excludeDomains;
  if ($excludeDomainFile) {
      my @objects = readObjects($excludeDomainFile);
      push @{$res{'exclude-domain'}}, @objects if scalar(@objects)>0;
   }


  if ($command eq "server" ){
    $res{'all'} = $res{'server'} = 1;

    die "'from-file' option should not be specified with 'server' command" if $objectsFromFileName;
  }
  elsif( $command eq "resellers-name" || $command eq "resellers-id" ||
         $command eq "clients-name" || $command eq "clients-id" ||
         $command eq "domains-name" || $command eq "domains-id" )
  {
     if( $objectsFromFileName ){
         my @objects = readObjects($objectsFromFileName);
         $res{$command} = \@objects;
     }
     elsif( scalar(@ARGV)>0 ){ $res{$command} = \@ARGV; }
     else{ $res{ "$command-all" } = 1; }
  }
  elsif( $command eq 'export-dump-as-file' ){
    if( not $dumpfilename ){
      usage(1);
    }
    $res{'export-dump-file'} = $dumpfilename;
    die "option '--output-file' required" if not $outputfile;
  }
  else{
    die "Unknown command '$command'";
  }
  return %res;
}

my %multipliers = ( '' => 1,
                    'k' => 1024,
                    'm' => 1024*1024,
                    'g' => 1024*1024*1024,
                    't' => 1024*1024*1024*1024 );

sub parseSize {
  my ($size) = @_;
  if ($size =~ /^=?(\d+)([kmgt]?)$/i) {
    return $1 * $multipliers{lc($2)};
  }
  return;
}

sub readObjects {
  my ($filename) = @_;
  open OBJECTS, "$filename" or die "Unable to open $filename";
  my @objects = <OBJECTS>;
  chomp @objects;
  close OBJECTS;
  return @objects;
}



sub perform {
  my (%settings) = @_;

  if( exists $settings{'output-file'} ) {
    die "Unable to backup directed to stdout" if $settings{'output-file'} eq '-';
  }

  my $dumpLogPath;
  if (defined $settings{'session-path'}) {
    $dumpLogPath = "$settings{'session-path'}/psadump.log";
    Logging::setXmlLogging();
  }
  
  Logging::open($dumpLogPath, $ENV{'CUSTOM_LOG'});

  if ($settings{'verbose'} > 1) {
    Logging::setVerbosity($settings{'verbose'} > 2 ? 5 : $settings{'verbose'});
  } else {
    Logging::setVerbosity(1);
  }
  
  my $status = ( defined $settings{'session-path'} )
    ? DumpStatus::createMigration( "$settings{'session-path'}/dump-status.xml" )
    : DumpStatus::createBackup();

  my $psaConf = SpecificConfig->new();
  my $psadumpdir = $psaConf->get( 'DUMP_D' );
  my $migrationMode;
  my $suspend;
  die "Cannnot determine dump directory from psa.conf or directory does not exists [$psadumpdir]!" unless -d $psadumpdir;

  my $dumpdir;

  if ( exists $settings{'backup-node'}) {
    $dumpdir = $settings{'backup-node'};
  } else {
    $dumpdir = $psadumpdir;
  }

  if( exists $settings{'migration-mode'} ){
    $migrationMode = 1;
     die "Migration mode requires session-path is set!" if not defined $settings{'session-path'};
    $dumpdir = $settings{'session-path'};
  }

  if( exists $settings{'suspend'} ){
     $suspend = 1;
     die "Suspend option requires session-path is set!" if not defined $settings{'session-path'};
  }

  if (exists $settings{'export-dump-file'} ) {
    my $files = executeExportDumpFile( $dumpdir, \%settings );
    if( defined $settings{'session-path'} ){
      open DUMP_RES, "> $settings{'session-path'}/dump-name";
      foreach my $fileInfo(@{$files}) { print DUMP_RES "$fileInfo\n"; }
      close DUMP_RES;
    }
    else{
      print STDERR "Output files\n";
      foreach my $fileInfo(@{$files}) { print STDERR "$fileInfo\n"; }
    }
    return 0;
  }

  my $space_reserved = 30*1024*1024;
  my $storage = Storage::Storage::createFileStorage( ($settings{'gzip'} and not exists $settings{'get-size'})
                                                   , $dumpdir
                                                   , $settings{'split-size'}
                                                   , (($migrationMode or exists $settings{'no-sign'}) ? undef : 1)
                                                   , $space_reserved
                                                   , $settings{'passive'} );

  my $agent = PleskX->new($storage, $status, $agentsShareDir, $settings{'skip-logs'});

  if( exists $settings{'profile-name'} ){
    die "Can not use profile name with migration mode" if $migrationMode;
    if( exists $settings{'profile-id'} ) {
      $agent->setBackupProfileFileName( $settings{'profile-name'}, $settings{'profile-id'} );
    }
    else{
      $agent->setBackupProfileFileName( $settings{'profile-name'} );
    }
  }
  if( exists $settings{'owner-uid'} ) {
     $agent->setBackupOwnerGuid( $settings{'owner-uid'}, ( exists $settings{'owner-type'} ? $settings{'owner-type'} : '' ) );
  }
  else{
     $agent->setBackupOwnerGuid();
  }

  $agent->turnOnMigrationMode() if $migrationMode;
  $agent->turnOnListingOnlyMode() if $migrationMode;
  $agent->setDumpWholeVHost() if not exists $settings{'no-whole-vhost'};
  $agent->setDumpWholeMail() if not exists $settings{'no-whole-mail'};

  $agent->setSuspend( $suspend, $settings{'session-path'} ) if ($suspend and not exists $settings{'get-size'} );

  $agent->setDescription( $settings{'description'} ) if exists $settings{'description'};

  if ($settings{'only-mail'}) {
    $agent->setDumpType($PleskX::ONLY_MAIL);
  }
  if ($settings{'only-hosting'}) {
    $agent->setDumpType($PleskX::ONLY_HOSTING);
  }
#  if ($settings{'only-database'}) {
#    $agent->setDumpType($PleskX::ONLY_DATABASE);
#  }
  if ($settings{'configuration'}) {
    $agent->setDumpType($PleskX::CONFIGURATION);
  }

  if (exists $settings{'all'}) {
    $agent->selectAll();
    $agent->selectAdminInfo();
    $agent->selectServerSettings();
  }
  if (exists $settings{'resellers-name-all'} or exists $settings{'resellers-id-all'}) {
    $agent->selectAllResellers();
  }
  if (exists $settings{'clients-name-all'} or exists $settings{'clients-id-all'} ) {
    $agent->selectAllClients();
  }
  if (exists $settings{'domains-name-all'} or exists $settings{'domains-id-all'} ) {
    $agent->selectAllDomains();
  }
  if (exists $settings{'resellers-name'}) {
    $agent->selectResellers(@{$settings{'resellers-name'}});
  }
  if (exists $settings{'clients-name'}) {
    $agent->selectClients(@{$settings{'clients-name'}});
  }
  if (exists $settings{'domains-name'}) {
    $agent->selectDomains(@{$settings{'domains-name'}});
  }
  if (exists $settings{'resellers-id'}) {
    $agent->selectResellersById( @{$settings{'resellers-id'}} );
  }
  if (exists $settings{'clients-id'}) {
    $agent->selectClientsById( @{$settings{'clients-id'}} );
  }
  if (exists $settings{'domains-id'}) {
    $agent->selectDomainsById( @{$settings{'domains-id'}} );
  }
  if( exists $settings{'server'} ){
    $agent->selectServerSettings();
  }

  if( exists $settings{'exclude-reseller'} ) {
      $agent->excludeResellers( @{$settings{'exclude-reseller'}} );
  }
  if( exists $settings{'exclude-client'} ) {
      $agent->excludeClients( @{$settings{'exclude-client'}} );
  }
  if( exists $settings{'exclude-domain'} ) {
      $agent->excludeDomains( @{$settings{'exclude-domain'}} );
  }

  if( exists $settings{'get-size'} ){
    my $returnCode = 0;
    try {
      Logging::setVerbosity(4);
      my $res = $agent->getSize();
      print $res;
    }
    catch Error with {
      my $error = shift;
      $agent->Cleanup();
      $storage->CleanupFiles();
      my $errmsg = "Unable to get backup size";
      print STDERR "$errmsg: $error\n";
      Logging::debug("$errmsg: $error");
      Logging::error($errmsg,'fatal');
      $returnCode = 3;
    };
    if ($returnCode != 0) {
      return $returnCode;
    }

    $status->finish();
    Logging::close();

    return 0;
  }

  my $pid = $$;
  local $SIG{INT} = sub{ $storage->CleanupFiles() if $$==$pid; die "The dump terminated unexpected by signal"; };
  my $returnCode = 0;
  try {
    my $res = $agent->dump();
    if ($res!=0) {
      Logging::error("Dump failed");
      $returnCode = 1;
    }
  } catch Error with {
    my $error = shift;
    $agent->Cleanup();
    $storage->CleanupFiles();
    my $errmsg = "Unable to create dump";
    print STDERR "$errmsg: $error\n";
    Logging::debug("$errmsg: $error");
    Logging::error($errmsg,'fatal');
    $returnCode = 1;
  };
  if ($returnCode != 0) {
    return $returnCode;
  }

  local $SIG{INT} = 'DEFAULT';

  checkDump($storage, $dumpdir, \%settings) if (!$migrationMode && (!exists $settings{'backup-node'} || exists $settings{'validate-by-schema'}));

  my $mainFileName = $storage->getMainDumpXmlFile();
  my $mainFileFolder = $storage->getFilePathFromId($storage->getMainDumpXmlRelativePath());
  $mainFileFolder = "/$mainFileFolder" if $mainFileFolder;
  $mainFileFolder .= '/' if $mainFileFolder && substr($mainFileFolder, -1, 1 ) ne '/';

  my @printFileInfo;
  # upload to ftp
  if( defined $settings{'ftp'} ) {
      print "\nUploading backup to ftp\n" if $settings{'verbose'};

      my @files = $storage->getDumpFiles( $storage->getFilePathFromId( $mainFileName ) );

      my $prefix = '';
      my $thisProfileName = $mainFileName;
      if( $mainFileName =~ /(.*?)_info_(\d{10}).xml$/ ){
        $thisProfileName = $1;
        $prefix = $2;
      }
      else {
        Logging::error( "Invalid dump file name '$mainFileName'. Cannot determine backup prefix." );
      }

      my %ftp = %{$settings{'ftp'}};
      my $ftpOutputFileName;
      my $result = exportDumpToFtp(\%ftp, $thisProfileName, $prefix, $settings{'verbose'}, $storage->getMainDumpXmlRelativePath(), \$ftpOutputFileName, $settings{'session-path'}, $settings{'split-size'});
      if ($result) {
          deleteLocalDump($storage->getMainDumpXmlRelativePath(), $settings{'verbose'}, $settings{'session-path'});
      }

      if( exists $settings{'dump-rotation'}  ) { #rotate dump at FTP
          try {
              Logging::debug( "Rotate dump '$ftpOutputFileName' at FTP" );
              my $ftpUrl = "$ftp{'protocol'}://$ftp{'login'}\@$ftp{'server'}";
              $ftpUrl .= ":$ftp{'port'}" if ($ftp{'port'});
              $ftpUrl .= "/" . $ftp{'path'} if ($ftp{'path'});
              rotateDump( $ftpUrl, $ftpOutputFileName , $settings{'dump-rotation'}, $agent->getBackupOwnerGuid(), 0,
                (exists $settings{'session-path'} ? $settings{'session-path'} : undef ), (exists $ftp{'passive'} ? 1 : undef), $settings{'verbose'} );
              Logging::debug( "The dump have been rotated" );
          } catch Error with {
               my $error = shift;
               Logging::error( "Unable to rotate dump: $error", 'UtilityError' );
               print STDERR "Unable to rotate dump: $error\n";
          };
      }
   }
  elsif( defined $settings{'output-file'} ){
       Logging::debug( "Export dump to file '$settings{'output-file'}'" );
       my $dumpPrefix = '';
       my $dumFileName = '';
       if( "$mainFileFolder$mainFileName" =~ /(.*\/)?(.*)_info_(\d{10}).*.xml/ ){
         $dumFileName = $2;
         $dumpPrefix = $3;
       }
       else {
         die "Invalid dump file name '$mainFileFolder$mainFileName'. Please give path to <info>.xml!";
       }

       exportDumpToLocal($settings{'output-file'}, $dumFileName, $dumpPrefix, $settings{'verbose'}, $storage->getMainDumpXmlRelativePath(), $settings{'session-path'}, $settings{'split-size'});
       $storage->CleanupFiles();
   }
  elsif ( defined $settings{'backup-node'}) {
    $storage->CleanupFiles();
    $ENV{'DUMP_STORAGE_PASSWD'} = $settings{'ftp-password'};
    checkDump($storage, $dumpdir, \%settings) if (!$migrationMode && (!exists $settings{'validate-by-schema'}));

    if (exists $settings{'dump-rotation'}) {
      try {
        Logging::debug("Rotate dumps at backup node");
        rotateDump(
          $settings{'backup-node'},
          "$mainFileFolder$mainFileName",
          $settings{'dump-rotation'},
          $agent->getBackupOwnerGuid(),
          1,
          (exists $settings{'session-path'} ? $settings{'session-path'} : undef),
          (exists $settings{'passive'} ? 1 : undef),
          $settings{'verbose'}
        );
        Logging::debug("The dump have been rotated");
      } catch Error with {
        my $error = shift;
        Logging::error("Unable to rotate dump: $error", 'UtilityError');
        print STDERR "Unable to rotate dump: $error\n";
      };
    }
  }
  else{
    if( !$migrationMode ){
       push @printFileInfo, "$mainFileFolder$mainFileName";
       if( exists $settings{'dump-rotation'} && !$migrationMode ){
           try {
                Logging::debug( "Rotate dump" );
                rotateDump( "$dumpdir$mainFileFolder", $storage->getMainDumpXmlFile(), $settings{'dump-rotation'}, $agent->getBackupOwnerGuid(), 1,
                            (exists $settings{'session-path'} ? $settings{'session-path'} : undef ), undef, $settings{'verbose'} );
                Logging::debug( "The dump have been rotated" );
         } catch Error with {
            my $error = shift;
            Logging::error( "Unable to rotate dump: $error" );
            print STDERR "Unable to rotate dump: $error\n";
         };
      }
    }
  }

  $status->finish();
  Logging::close();
  if( defined $settings{'session-path'} ){
    if( !$migrationMode ){
      open DUMP_RES, "> $settings{'session-path'}/dump-name";
      foreach my $fileInfo(@printFileInfo) { print DUMP_RES "$fileInfo\n"; }
      close DUMP_RES;
    }
  }
  else{
    print STDERR "Output files\n";
    foreach my $fileInfo(@printFileInfo) { print STDERR "$fileInfo\n"; }
  }

  return 0;
}

sub exportDumpToFtp{
    my ($ftp, $dumpFileName, $prefix, $verbose, $relativeMainXmlFileName, $outputFileName, $sessionPath, $splitSize) = @_;

    print "\nUploading backup to ftp\n" if $verbose;

    my @ftpFiles;

    my $ftpAdr = $ftp->{'protocol'}."://".$ftp->{'login'}."@".$ftp->{'server'};
    $ftpAdr .= ":$ftp->{port}" if $ftp->{port};
    $ftpAdr .= "/$ftp->{path}" if $ftp->{path};
    $ftpAdr .= '/' if substr( $ftpAdr, -1, 1 ) ne '/';


    my $filename = $ftp->{'file'};
    if (!$filename){
       $filename = $dumpFileName;
       $filename .= "_" . $prefix;
       $filename .= '.tar';
    }
    if (defined($outputFileName) && length(${$outputFileName}) == 0) {
        ${$outputFileName} = $filename;
    }

    Logging::debug( "Uploading file '$dumpFileName' to ftp file '$filename'\n" );

    $ENV{'DUMP_STORAGE_PASSWD'} = $ftp->{'password'};
    my $cmd = "$productRootD/admin/bin/pmm-ras --export-dump-as-file --dump-specification=\"$relativeMainXmlFileName\" --dump-file-specification=\"$ftpAdr$filename\"";
    $cmd .= " --use-ftp-passive-mode" if exists $ftp->{'passive'};
    $cmd .= " --debug --verbose" if $verbose;
    if (defined $sessionPath && length($sessionPath) > 0) {
        $cmd .= " --session-path=\"$sessionPath\"";
    }
    if (defined $splitSize && length($splitSize) > 0) {
        $cmd .= " --split-size=\"$splitSize\"";
    }
    Logging::debug("Execute: $cmd");
    my $cmdResult = `$cmd`;
    my $retCode = $? >> 8;
    if ($retCode != 0) {
        Logging::error("Can't upload file '$relativeMainXmlFileName' to ftp. Error code: $retCode");
        Logging::debug("Uploader output: $cmdResult");
        return;
    } else {
        push @ftpFiles, "$ftpAdr$filename";
    }

    return \@ftpFiles;
}

sub exportDumpToLocal{
    my ($outputFileName, $dumpFileName, $prefix, $verbose, $relativeMainXmlFileName, $sessionPath, $splitSize) = @_;

    my @ftpFiles;
    my $err;
    my $outputDir = '';
    my $outputFile = '';

    if (substr($outputFileName, -1) eq '/') {
        $outputDir = $outputFileName;
        $outputFile = $dumpFileName;
        $outputFile .= "_" . $prefix;
        $outputFile .= '.tar';
    } else {
        $outputFile = $outputFileName;
        if ($outputFile =~ /(.*\/)(.*)/ ){
          $outputDir = $1;
        } else {
            if (not $outputFile =~ /\/.*/) {
                $outputDir = '';
            } else {
                $outputDir = '/';
            }
        }
        $outputFile = substr( $outputFile, length($outputDir) );
    }

    my $cmd = "$productRootD/admin/bin/pmm-ras --export-dump-as-file --dump-specification=\"$relativeMainXmlFileName\" --dump-file-specification=\"$outputDir$outputFile\"";
    $cmd .= " --debug --verbose" if $verbose;
    if (defined $sessionPath && length($sessionPath) > 0) {
        $cmd .= " --session-path=\"$sessionPath\"";
    }
    if (defined $splitSize  && length($splitSize) > 0) {
        $cmd .= " --split-size=\"$splitSize\"";
    }
    Logging::debug("Execute: $cmd");
    my $cmdResult = `$cmd`;
    my $retCode = $? >> 8;
    if ($retCode != 0) {
        Logging::error("Can't export file '$relativeMainXmlFileName' to $outputDir$outputFile. Error code: $retCode");
        Logging::debug("Export output: $cmdResult");
        return;
    } else {
        push @ftpFiles, "$outputDir$outputFile";
    }

    return \@ftpFiles;
}

sub executeExportDumpFile {
    my($dumpdir, $settings) = @_;

    my $dumpfile = $settings->{'export-dump-file'};

    my $fullPathToDump = $dumpfile;

    if ($dumpfile =~ /\/.*/){
        if (index( $dumpfile, "$dumpdir" ) == 0) {
            $dumpfile = substr( $dumpfile, length($dumpdir) + 1 )
        }
    }

    my ($targetDir, $targetFile, $useExt);
    $useExt = 0;
    my $fileExport = 0;

    if (defined $settings->{'ftp'}) {
        if (not $settings->{'ftp'}{'file'}) {
            $useExt = 1;
        }
    }
    else {
        if( substr($settings->{'output-file'},-1) eq '/'){
            $useExt = 1;
        }
    }

    my $fh;
    ($fh, $targetFile ) = File::Temp::tempfile( "$productRootD/PMM/tmp/backupXXXXXX" );
    close( $fh );

    if ($targetFile =~ /(.*\/)(.*)/) {
        $targetDir = $1;
    } else{
        if (not $targetFile =~ /\/.*/) {
            $targetDir = '';
        } else {
            $targetDir = '/';
        }
    }

    $targetFile = substr( $targetFile, length($targetDir) );

    my $relativeDumpPath = '';
    my $dumpPrefix = '';
    my $dumFileName = '';
    if ($dumpfile =~ /(.*\/)?(.*)_info_(\d{10}).*.xml/) {
        $relativeDumpPath = $1;
        $dumFileName = $2;
        $relativeDumpPath = '' if not $relativeDumpPath;
        $relativeDumpPath = "/$relativeDumpPath" if $relativeDumpPath;
        $dumpPrefix = $3;
    } else {
        die "Invalid dump file name '$dumpfile'. Please give path to <info>.xml!";
    }
    my $dumpProfileName = $dumFileName;
    my $idx = index( $dumpProfileName, "_" );
    $dumpProfileName = substr( $dumpProfileName, 0, $idx ) if $idx>0;

    my $resultFiles;
    if (defined $settings->{'ftp'}) {
        my %ftp = %{$settings->{'ftp'}};
        $resultFiles = exportDumpToFtp(\%ftp, $dumFileName, $dumpPrefix, $settings->{'verbose'}, $dumpfile, undef, $settings->{'session-path'}, $settings->{'split-size'});
    } else{
        $resultFiles = exportDumpToLocal($settings->{'output-file'}, $dumFileName, $dumpPrefix, $settings->{'verbose'}, $dumpfile, $settings->{'session-path'}, $settings->{'split-size'});
    }
    if (not $resultFiles) {
        Logging::error( "The export have been made successfully but can not be exported because of errors above." );
        die "Export dump failed";
    }
    return $resultFiles;
}

sub checkDump {
  my ($storage, $dumpdir, $settings) = @_;
  my $checkDumpRes = 0;

  try {
    Logging::debug( "Check dump" );
    my $mainFileRelativePath = $storage->getMainDumpXmlRelativePath();
    if (exists $settings->{'validate-by-schema'}) {
      my $cmd = AgentConfig::xmllintBin() . " --noout --schema $settings->{'validate-by-schema'} $dumpdir/$mainFileRelativePath";
      Logging::debug( "Execute: $cmd" );
      $checkDumpRes = $? >> 8;
    } else {
      my $dumpDir = $storage->getFullOutputPath();
      my $sessionPath = (exists $settings->{'session-path'} ? $settings->{'session-path'} : undef );
      Logging::debug( "Check dump started ( File: '$mainFileRelativePath' in the repository '$dumpDir' )" );

      my $cmd = "$productRootD/admin/bin/pmm-ras --get-dump-info --dump-file-specification=$mainFileRelativePath --with-feedback";
      $cmd .= " --session-path=$sessionPath --verbose" if $sessionPath;
      $cmd .= " --dump-storage=".$settings->{'backup-node'} if defined $settings->{'backup-node'};
      $cmd .= " --use-ftp-passive-mode" if defined $settings->{'passive'};
      Logging::debug( "Execute: $cmd" );
      my $cmdResult = `$cmd` or die "Cannot execute :$cmd";
      my $retCode = $? >> 8;
      Logging::debug( "The check dump is executed with errorcode '$retCode'" );
      Logging::debug( "The check dump output:$cmdResult" );
      $checkDumpRes = PmmCli::parseCheckDumpResult( $cmdResult );
    }
    Logging::debug( "The check dump return '$checkDumpRes'" );
  }
  catch Error with {
    my $error = shift;
    Logging::error( "Unable to check dump: $error",'CheckDump' );
    print STDERR "Unable to check dump: $error\n";
    $checkDumpRes = 1;
  };

  if( $checkDumpRes!=0 ){
    Logging::error( "The dump have been invalidated by check-dump operation",'CheckDump' );
    die "The check dump failed with code '$checkDumpRes'. The dump can contain invalid data!";
  }
  Logging::debug( "The dump have been validated successfully" );
}

sub rotateDump{
  my( $dumpDir, $dumpFileName, $dumpRotation, $ownerguid, $structured, $sessionPath, $passiveMode, $verbose ) = @_;
  Logging::debug( "Dump rotation started File: '$dumpFileName' in the repository '$dumpDir'. Set backup's count to '$dumpRotation'" );
  my $cmd = "$productRootD/admin/bin/pmm-ras --rotate-dump --dump-rotation=$dumpRotation --guid=$ownerguid --dump-specification=$dumpFileName";
  $cmd .= " --dump-storage=$dumpDir";
  $cmd .= " --storage-structured" if $structured;
  $cmd .= " --session-path=$sessionPath --verbose" if $sessionPath;
  $cmd .= " --debug" if $verbose;
  $cmd .= " --use-ftp-passive-mode" if defined $passiveMode;
  Logging::debug( "Execute: $cmd" );
  my $cmdResult = `$cmd`;
  my $retCode = $? >> 8;
  Logging::debug( "The dump rotation is executed with errorcode '$retCode'" );
  Logging::debug( "The dump rotation output: $cmdResult" ) if $cmdResult;
  die "The dump rotation is failed with code '$retCode'" if $retCode!=0;
  return;
}

sub deleteLocalDump{
    my ($dumpFileName, $verbose, $sessionPath) = @_;
    Logging::debug( "Local dump removing started File: '$dumpFileName'" );
    my $cmd = "$productRootD/admin/bin/pmm-ras --delete-dump --dump-specification=$dumpFileName";
    $cmd .= " --session-path=$sessionPath" if $sessionPath;
    $cmd .= " --verbose --debug" if $verbose;
    Logging::debug( "Execute: $cmd" );
    my $cmdResult = `$cmd`;
    my $retCode = $? >> 8;
    Logging::debug( "The local dump removing is executed with errorcode '$retCode'" );
    Logging::debug( "The local dump removing output: $cmdResult" ) if $cmdResult;
    die "The local dump removing is failed with code '$retCode'" if $retCode!=0;
    return;
}

sub processError{
  my ( $data, $isError, $prevName, $prevType, $prevObject, $execRes ) = @_;
  my ( $name, $type, $object );
  return  if !$data;
  return  if ref($data) ne 'ARRAY';
  return  if scalar(@{$data})==0;
  $name = "backup";
  $type = "backupowner";
  if( scalar(@{$data})>1 ){
   $name = $data->[1];
   $type = $data->[2] if scalar(@{$data})>2;
  }

  if( ! $$prevObject || !$$prevName || $$prevName ne $name || !$$prevType || $$prevType ne $type ){
    $object = XmlNode->new( 'object' );
    $object->setAttribute( 'name', $name );
    $object->setAttribute( 'type', $type );
    $$prevObject = $object;
    $execRes->addChild( $object );
  }
  else{
    $object = $$prevObject;
  }
  my $msg = XmlNode->new( 'message' );
  $msg->setAttribute( 'code', 'msgtext' );
  $msg->setAttribute( 'severity', ( $isError ? 'error' : 'warning' ) );
  $msg->setText( $data->[0] );
  $object->addChild( $msg );
  return;
}

sub writeMigrationResult{
  my $sessionPath = $agentSessionPath;
  if( $sessionPath && -d $sessionPath ){
    Logging::serializeXmlLog("$sessionPath/migration.result");
  }
  return;
}

my $writeResult = 1;
sub main {  
  my %settings;
  my $returnCode = 0;
  try {
    %settings = parseOptions();
    $writeResult = not exists $settings{'get-size'};
  } catch Error with {
    my $error = shift;
    my $errmsg = "Unable to parse options";
    print STDERR "$errmsg: $error\n";
    Logging::debug("$errmsg: $error");
    Logging::error($errmsg,'fatal');
    $returnCode = 2;
  };
  if ($returnCode != 0) {
    return $returnCode;
  }

  $returnCode = 0;
  try {
    $returnCode = perform(%settings);
  } catch Error with {
    my $error = shift;
    my $errmsg = "Runtime error";
    print STDERR "$errmsg: $error\n";
    Logging::debug("$errmsg: $error\n" . $error->stacktrace() . "\n");
    Logging::error("$errmsg: $error", 'fatal');
    $returnCode = 1;
  };
  return $returnCode;
}

my $exitcode = main();
writeMigrationResult() if $writeResult;
exit($exitcode);

# Local Variables:
# mode: cperl
# cperl-indent-level: 2
# indent-tabs-mode: nil
# tab-width: 4
# End:
