#!/usr/bin/perl
# Copyright 1999-2015. 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

  --incremental  Make incremental backup. If there is no full backup, it will be created instead of incremental

  --include-increments
                 Used with export-dump-as-file command to perform whole dump export including all incremental dumps.
  --keep-local-backup
                 Keep created backup in server storage if export to FTP was failed.

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 '');
      }

      $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;
  $res{'include-increments'} = 0;

  my ( $split, $outputfile, $ftplogin, $ftppwd, $ftppasv, $backupnode );
  my ( $objectsFromFileName, $profileId, $profileName, $incrementalCreationDate, $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,
                         'include-increments' => sub { $res{'include-increments'} = 1 },
                         "incremental" => sub { $res{'incremental'} = 1 },
                         "keep-local-backup" => sub { $res{'keep-local-backup'} = 1 },

                         "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,
                         'incremental-creation-date=s' => \$incrementalCreationDate,
                         "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{'incremental-creation-date'} = $incrementalCreationDate if defined $incrementalCreationDate;
  $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 $stdin = "";
  if (tell(\*STDIN) != -1) {
    $stdin = do { local $/; <STDIN> };
  }
  Logging::debug("STDIN=$stdin");
  foreach my $env (keys %ENV) { Logging::debug("ENV[$env] = $ENV{$env}"); }

  my $backupParameters = eval {
    use JSON::XS;
    return JSON::XS->new->utf8->pretty->decode($stdin);
  };
  if ($@) {
    use Data::Dumper;
    Logging::debug(Dumper $@);
    $backupParameters = undef;
  };


  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'}, $backupParameters);

  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;
  }

  initIncrementalBackupSupport(\%settings, $agent, $dumpdir) if not $migrationMode;

  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 '/';

  # store miration.result to discovered
  my $migrationResultPath = writeMigrationResult();
  if ($migrationResultPath and -e $migrationResultPath) {
    my $posExtBegin = rindex( $mainFileName, '.xml' );
    my $mainFileNameWithoutExt = substr($mainFileName, 0, $posExtBegin);
    my $severity = uc(Logging::getSeverity());
    $storage->moveFileToDiscovered($migrationResultPath, "dumpresult_$severity", $mainFileFolder, $mainFileNameWithoutExt);
  }

  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 $ftpOutputFileName = getExportDumpFileName($storage->getMainDumpXmlRelativePath());
      die "Unable to export dump. Invalid dump file name" if (not $ftpOutputFileName);

      my %ftp = %{$settings{'ftp'}};
      my $result = exportDumpToFtp(\%ftp, $settings{'verbose'}, $storage->getMainDumpXmlRelativePath(), $ftpOutputFileName, $settings{'session-path'}, $settings{'split-size'}, 0);
      if ($result) {
        push @printFileInfo, $ftpOutputFileName;
        deleteLocalDump($storage->getMainDumpXmlRelativePath(), $settings{'verbose'}, $settings{'session-path'});
      } else {
        if (not $settings{'keep-local-backup'}) {
          deleteLocalDump($storage->getMainDumpXmlRelativePath(), $settings{'verbose'}, $settings{'session-path'});
        } else {
          push @printFileInfo, "$mainFileFolder$mainFileName";
        }
      }

      if( exists $settings{'dump-rotation'}  ) { #rotate dump at FTP
          try {
              Logging::debug( "Rotate dump '$ftpOutputFileName' at FTP" );
              my $ftpUrl = getStorageUrlFromFtpSettings(\%ftp);
              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}(_\d{10})?).*.xml/ ){
         $dumFileName = $2;
         $dumpPrefix = $3;
       }
       else {
         die "Invalid dump file name '$mainFileFolder$mainFileName'. Please give path to <info>.xml!";
       }

       my $files = exportDumpToLocal($settings{'output-file'}, $dumFileName, $dumpPrefix, $settings{'verbose'}, $storage->getMainDumpXmlRelativePath(), $settings{'session-path'}, $settings{'split-size'}, 0);
       if (scalar $files > 0) {
        @printFileInfo = map { basename($_) } @{$files};
        $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'}));

    my $sessionPath = exists($settings{'session-path'}) ? $settings{'session-path'} : undef;
    my $passiveMode = exists($settings{'passive'}) ? 1 : undef;
    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,
          $sessionPath,
          $passiveMode,
          $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";
      };
    }

    foreach my $domainName (@{$agent->{domains}}) {
      my $domain = DAL::getDomainPtr($domainName);
      if ($domain) {
        syncCache($settings{'backup-node'}, 'domain', $domain->{'name'}, $sessionPath, $passiveMode);
      }
    }
  }
  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 getExportDumpFileName {
  my ($mainDumpFilePath) = @_;

  my $exportDumpFileName;
  if ($mainDumpFilePath =~ /([\/\\]+([^\/\\]+)[\/\\]+)?([^\/\\]*_)info_((\d{10}(_\d{10})?)).xml$/){
    if ($2) {
      $exportDumpFileName = $3.$2."_".$4.".tar";
    } else {
      $exportDumpFileName = $3.$4.".tar";
    }
  }

  return $exportDumpFileName;
}

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

  my ($baseTimestamp, $lastIncrementFile, $type, $name, $guid);
  if (exists($settings->{'incremental'})) {
    if ($agent->{dump_all} && $agent->{dump_all} == 1) {
      $type = 'server';
    } elsif (exists $agent->{resellers} && scalar @{$agent->{resellers}} > 0) {
      if (scalar @{$agent->{resellers}} == 1) {
        $type = 'reseller';
        $name = $agent->{resellers}->[0];
        $guid = $agent->getClientGuid($name);
      } else {
        $type = 'server';
      }
    } elsif (exists $agent->{clients} && scalar @{$agent->{clients}} > 0) {
      if (scalar @{$agent->{clients}} == 1) {
        $type = 'client';
        $name = $agent->{clients}->[0];
        $guid = $agent->getClientGuid($name);
      } else {
        $type = 'server';
      }
    } elsif (exists $agent->{domains} && scalar @{$agent->{domains}} > 0) {
      if (scalar @{$agent->{domains}} == 1) {
        $type = 'domain';
        $name = $agent->getDomainAsciiName($agent->{domains}->[0]);
        $guid = $agent->getDomainGuid($agent->{domains}->[0]);
      } else {
        $type = 'server';
      }
    } else {
      return;
    }
  } elsif (exists($settings->{'incremental-creation-date'})) {
    $baseTimestamp = $settings->{'incremental-creation-date'};
  } else {
    return;
  }

  Logging::debug("Initialize incremental backup support");

  $guid = $agent->getBackupOwnerGuid() if ($type eq 'server');

  # Prepare common parameters for pmm-ras
  my $dumpStorage;
  my $cmdExtraParams = "";

  if (defined $settings->{'ftp'}) {
    my $ftp = $settings->{'ftp'};

    $dumpStorage = getStorageUrlFromFtpSettings($ftp);
    $dumpStorage .= '/' if substr( $dumpStorage, -1, 1 ) ne '/';
  } elsif (defined $settings->{'output-file'}) {
    if (not -d $settings->{'output-file'}) {
      die "The option --output-file must specify existing directory path to store incremental backup";
    }

    $dumpStorage = $settings->{'output-file'};
  } else {
    $dumpStorage = $dumpdir;
  }

  if (defined($settings->{'backup-node'})) {
    $ENV{'DUMP_STORAGE_PASSWD'} = $settings->{'ftp-password'};
    $cmdExtraParams .= " --use-ftp-passive-mode" if $settings->{'passive'};
  } elsif (defined($settings->{'ftp'})) {
    my $ftp = $settings->{'ftp'};
    $ENV{'DUMP_STORAGE_PASSWD'} = $ftp->{'password'} if exists $ftp->{'password'};
    $cmdExtraParams .= " --use-ftp-passive-mode" if exists $ftp->{'passive'};
  }

  $cmdExtraParams .= " --debug --verbose" if $settings->{'verbose'};
  if (defined $settings->{'session-path'} && length($settings->{'session-path'}) > 0) {
    $cmdExtraParams .= " --session-path=\"" . $settings->{'session-path'} . "\"";
  }

  # Get base timestamp if not already passed via parameters
  if (not $baseTimestamp) {
    my $cmd = AgentConfig::pmmRasBin()." --get-incremental-base --dump-storage=\"$dumpStorage\" --dump-prefix=\"".$agent->getBackupPrefix()."\" --type=$type --guid=$guid";
    $cmd .= " --name=$name" if ($name);
    $cmd .= " --storage-structured" if (!$settings->{'ftp'} && !$settings->{'output-file'});
    $cmd .= $cmdExtraParams;

    Logging::debug("Execute: $cmd");
    my $stdout = `$cmd`;
    my $retCode = $? >> 8;
    if ($retCode != 0) {
        Logging::debug("Unable to find suitable full dump to create increment, so full dump will be created. Error: ".$stdout);
        return;
    }
    Logging::debug("Output:\n$stdout");

    my %baseInfo = split /[=\n]/, $stdout;
    $baseTimestamp = $baseInfo{'base-timestamp'} if ($baseInfo{'base-timestamp'});
    $lastIncrementFile = $baseInfo{'last-increment-path'} if ($baseInfo{'last-increment-path'});
  }

  return if (not $baseTimestamp);

  # For the case of exported dump extract previous increment index
  if (defined $settings->{'ftp'} || defined $settings->{'output-file'}) {
    if (not $lastIncrementFile) {
      Logging::warning("Unable to get last dump required for incremental backup, full dump will be created instead");
      return;
    }

    my $outDir = File::Temp::tempdir( AgentConfig::getBackupTmpDir() . "/iXXXXXX", CLEANUP => 1 );
    my $cmd = AgentConfig::pmmRasBin()." --extract-index --dump-storage=\"$dumpStorage\" --dump-file-specification=\"$lastIncrementFile\" --out-dir=$outDir".$cmdExtraParams;

    Logging::debug("Execute: $cmd");
    my $cmdResult = `$cmd`;
    my $retCode = $? >> 8;
    if ($retCode != 0) {
        Logging::warning("Unable to load previous backup index required for making incremental backup, full dump will be created instead");
        return;
    }

    $agent->setLastIndexPath($outDir);
  }

  $agent->setIncrementalCreationDate($baseTimestamp);
}

sub getStorageUrlFromFtpSettings {
  my ($ftp) = @_;
  my $url = $ftp->{'protocol'}."://".$ftp->{'login'}."@".$ftp->{'server'};
  $url .= ":$ftp->{port}" if $ftp->{port};
  $url .= "/$ftp->{path}" if $ftp->{path};
  return $url;
}

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

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

    my @ftpFiles;

    my $ftpAdr = getStorageUrlFromFtpSettings($ftp);
    $ftpAdr .= '/' if substr( $ftpAdr, -1, 1 ) ne '/';

    Logging::debug( "Uploading dump '$relativeMainXmlFileName' to ftp file '$outputFileName'\n" );

    $ENV{'DUMP_STORAGE_PASSWD'} = $ftp->{'password'};
    my $cmd = AgentConfig::pmmRasBin()." --export-dump-as-file --dump-specification=\"$relativeMainXmlFileName\" --dump-file-specification=\"$ftpAdr$outputFileName\"";
    $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\"";
    }
    if ($includeIncrements) {
        $cmd .= " --include-increments";
    }
    Logging::debug("Execute: $cmd");
    my $cmdResult = `$cmd`;
    my $retCode = $? >> 8;
    if ($retCode != 0) {
        Logging::error("Unable to upload dump to FTP, it remains in server repository and you can manually download and upload it to FTP. Upload diagnostic message: $cmdResult");
        return;
    } else {
        push @ftpFiles, "$ftpAdr$outputFileName";
    }

    return \@ftpFiles;
}

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

    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 = AgentConfig::pmmRasBin()." --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\"";
    }
    if ($includeIncrements) {
        $cmd .= " --include-increments";
    }
    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. $cmdResult");
        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 $ftpOutputFileName = ($settings->{'ftp'}->{'file'}) ? $settings->{'ftp'}->{'file'} : getExportDumpFileName($dumpfile);
        my %ftp = %{$settings->{'ftp'}};
        $resultFiles = exportDumpToFtp(
            \%ftp
            , $settings->{'verbose'}
            , $dumpfile
            , $ftpOutputFileName
            , $settings->{'session-path'}
            , $settings->{'split-size'}
            , $settings->{'include-increments'}
        );
    } else{
        $resultFiles = exportDumpToLocal(
          $settings->{'output-file'}
          , $dumFileName
          , $dumpPrefix
          , $settings->{'verbose'}
          , $dumpfile
          , $settings->{'session-path'}
          , $settings->{'split-size'}
          , $settings->{'include-increments'}
        );
    }
    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 = [AgentConfig::pmmRasBin(), '--get-dump-info', "--dump-file-specification=$mainFileRelativePath", '--with-feedback'];
      push(@{$cmd}, "--session-path=$sessionPath", '--verbose') if $sessionPath;
      push(@{$cmd}, '--dump-storage='.$settings->{'backup-node'}) if defined $settings->{'backup-node'};
      push(@{$cmd}, '--use-ftp-passive-mode') if defined $settings->{'passive'};
      Logging::debug('Execute: ' . join(' ', @{$cmd}));
      my ($stdout, $stderr);
      IPC::Run::run($cmd, '1>', \$stdout, '2>', \$stderr);
      my $retCode = $? >> 8;
      Logging::debug("The check dump is executed with errorcode '$retCode'");
      Logging::debug("The check dump stdout: $stdout");
      Logging::debug("The check dump stderr: $stderr");
      $checkDumpRes = PmmCli::parseCheckDumpResult($stdout);
    }
    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 = [AgentConfig::pmmRasBin(), "--rotate-dump", "--dump-rotation=$dumpRotation", "--guid=$ownerguid", "--dump-specification=$dumpFileName", "--dump-storage=$dumpDir"];
  push(@{$cmd}, '--storage-structured') if $structured;
  push(@{$cmd}, "--session-path=$sessionPath", '--verbose') if $sessionPath;
  push(@{$cmd}, '--debug') if $verbose;
  push(@{$cmd}, '--use-ftp-passive-mode') if defined $passiveMode;
  Logging::debug('Execute: ' . join(' ', @{$cmd}));
  my $stdout;
  IPC::Run::run($cmd, '1>', \$stdout);
  my $retCode = $? >> 8;
  Logging::debug( "The dump rotation is executed with errorcode '$retCode'" );
  Logging::debug("The dump rotation output: $stdout") if $stdout;
  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 = AgentConfig::pmmRasBin()." --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 syncCache{
  my ($dumpStorage, $objectType, $objectName, $sessionPath, $passiveMode) = @_;
  my $cmd = AgentConfig::pmmRasBin() . " --sync-cache --dump-storage=$dumpStorage --type=$objectType --name=$objectName --update-modification-time";
  $cmd .= " --session-path=$sessionPath --verbose" if $sessionPath;
  $cmd .= " --use-ftp-passive-mode" if $passiveMode;
  Logging::debug("Execute: $cmd");
  my $cmdResult = `$cmd`;
  my $retCode = $? >> 8;
  Logging::debug("Result ($retCode): $cmdResult");
  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;
  my $pathToResultFile = "$sessionPath/migration.result";
  if( $sessionPath && -d $sessionPath ){
    Logging::serializeXmlLog($pathToResultFile);
    return $pathToResultFile;
  }
  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:
