#!/usr/bin/perl
####################################################################################################################################
# pgBackRest - Reliable PostgreSQL Backup & Restore
####################################################################################################################################

####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';

# Convert die to confess to capture the stack trace
$SIG{__DIE__} = sub { Carp::confess @_ };

use File::Basename qw(dirname);

use lib dirname($0) . '/../lib';

use pgBackRest::Common::Exception;
use pgBackRest::Common::Exit;
use pgBackRest::Common::Lock;
use pgBackRest::Common::Log;
use pgBackRest::Config::Config;
use pgBackRest::Protocol::Helper;

####################################################################################################################################
# Run in eval block to catch errors
####################################################################################################################################
local $EVAL_ERROR = undef; eval
{
    ################################################################################################################################
    # Load command line parameters and config
    ################################################################################################################################
    my $bConfigResult = configLoad();

    # Display help and version
    if (commandTest(CMD_HELP) || commandTest(CMD_VERSION))
    {
        # Load module dynamically
        require pgBackRest::Config::ConfigHelp;
        pgBackRest::Config::ConfigHelp->import();

        # Generate help and exit
        configHelp($ARGV[1], $ARGV[2], commandTest(CMD_VERSION), $bConfigResult);
        exitSafe(0);
    }

    # Set test options
    if (optionTest(OPTION_TEST) && optionGet(OPTION_TEST))
    {
        testSet(optionGet(OPTION_TEST), optionGet(OPTION_TEST_DELAY), optionGet(OPTION_TEST_POINT, false));
    }

    ################################################################################################################################
    # Process archive-push command
    ################################################################################################################################
    if (commandTest(CMD_ARCHIVE_PUSH))
    {
        # Load module dynamically
        require pgBackRest::Archive::Push::Push;
        pgBackRest::Archive::Push::Push->import();

        exitSafe(new pgBackRest::Archive::Push::Push()->process($ARGV[1]));
    }

    ################################################################################################################################
    # Process archive-get command
    ################################################################################################################################
    if (commandTest(CMD_ARCHIVE_GET))
    {
        # Load module dynamically
        require pgBackRest::Archive::Get::Get;
        pgBackRest::Archive::Get::Get->import();

        exitSafe(new pgBackRest::Archive::Get::Get()->process());
    }

    ################################################################################################################################
    # Process remote commands
    ################################################################################################################################
    if (commandTest(CMD_REMOTE))
    {
        # Set log levels
        optionSet(OPTION_LOG_LEVEL_STDERR, PROTOCOL, true);
        logLevelSet(OFF, OFF, optionGet(OPTION_LOG_LEVEL_STDERR));

        if (optionTest(OPTION_TYPE, BACKUP) && !optionTest(OPTION_REPO_TYPE, REPO_TYPE_S3) && !-e optionGet(OPTION_REPO_PATH))
        {
            confess &log(ERROR, 'repo-path \'' . optionGet(OPTION_REPO_PATH) . '\' does not exist', ERROR_PATH_MISSING);
        }

        # Load module dynamically
        require pgBackRest::Protocol::Remote::Minion;
        pgBackRest::Protocol::Remote::Minion->import();

        # Create the remote object
        my $oRemote = new pgBackRest::Protocol::Remote::Minion(optionGet(OPTION_BUFFER_SIZE), optionGet(OPTION_PROTOCOL_TIMEOUT));

        # Acquire a remote lock (except for commands that are read-only or local processes)
        if (!(optionTest(OPTION_COMMAND, CMD_ARCHIVE_GET) || optionTest(OPTION_COMMAND, CMD_INFO) ||
              optionTest(OPTION_COMMAND, CMD_RESTORE) || optionTest(OPTION_COMMAND, CMD_CHECK) ||
              optionTest(OPTION_COMMAND, CMD_LOCAL)))
        {
            lockAcquire(optionGet(OPTION_COMMAND), undef, true);
        }

        # Process remote requests
        exitSafe($oRemote->process());
    }

    ################################################################################################################################
    # Process local commands
    ################################################################################################################################
    if (commandTest(CMD_LOCAL))
    {
        # Set log levels
        optionSet(OPTION_LOG_LEVEL_STDERR, PROTOCOL, true);
        logLevelSet(OFF, OFF, optionGet(OPTION_LOG_LEVEL_STDERR));

        # Load module dynamically
        require pgBackRest::Protocol::Local::Minion;
        pgBackRest::Protocol::Local::Minion->import();

        # Create the local object
        my $oLocal = new pgBackRest::Protocol::Local::Minion();

        # Process local requests
        exitSafe($oLocal->process());
    }

    ################################################################################################################################
    # Process check command
    ################################################################################################################################
    if (commandTest(CMD_CHECK))
    {
        # Load module dynamically
        require pgBackRest::Check::Check;
        pgBackRest::Check::Check->import();

        exitSafe(new pgBackRest::Check::Check()->process());
    }

    ################################################################################################################################
    # Process start/stop commands
    ################################################################################################################################
    if (commandTest(CMD_START))
    {
        lockStart();
        exitSafe(0);
    }
    elsif (commandTest(CMD_STOP))
    {
        lockStop();
        exitSafe(0);
    }

    # Check that the repo path exists
    require pgBackRest::Protocol::Storage::Helper;
    pgBackRest::Protocol::Storage::Helper->import();

    if (isRepoLocal() && !optionTest(OPTION_REPO_TYPE, REPO_TYPE_S3) && !storageRepo()->pathExists(''))
    {
        confess &log(ERROR, 'repo-path \'' . optionGet(OPTION_REPO_PATH) . '\' does not exist', ERROR_PATH_MISSING);
    }

    ################################################################################################################################
    # Process info command
    ################################################################################################################################
    if (commandTest(CMD_INFO))
    {
        # Load module dynamically
        require pgBackRest::Info;
        pgBackRest::Info->import();

        exitSafe(new pgBackRest::Info()->process());
    }

    ################################################################################################################################
    # Acquire the command lock
    ################################################################################################################################
    lockAcquire(commandGet());

    ################################################################################################################################
    # Open the log file
    ################################################################################################################################
    logFileSet(optionGet(OPTION_LOG_PATH) . '/' . optionGet(OPTION_STANZA) . '-' . lc(commandGet()));

    ################################################################################################################################
    # Process stanza-create command
    ################################################################################################################################
    if (commandTest(CMD_STANZA_CREATE) || commandTest(CMD_STANZA_UPGRADE))
    {
        if (!isRepoLocal())
        {
            confess &log(ERROR, commandGet() . ' command must be run on the backup host', ERROR_HOST_INVALID);
        }

        # Load module dynamically
        require pgBackRest::Stanza;
        pgBackRest::Stanza->import();

        exitSafe(new pgBackRest::Stanza()->process());
    }

    ################################################################################################################################
    # RESTORE
    ################################################################################################################################
    if (commandTest(CMD_RESTORE))
    {
        if (!isDbLocal())
        {
            confess &log(ERROR, 'restore command must be run on the db host', ERROR_HOST_INVALID);
        }

        # Load module dynamically
        require pgBackRest::Restore;
        pgBackRest::Restore->import();

        # Do the restore
        new pgBackRest::Restore()->process();

        exitSafe(0);
    }
    else
    {
        ############################################################################################################################
        # Make sure backup and expire commands happen on the backup side
        ############################################################################################################################
        if (!isRepoLocal())
        {
            confess &log(ERROR, 'backup and expire commands must be run on the backup host', ERROR_HOST_INVALID);
        }

        ############################################################################################################################
        # BACKUP
        ############################################################################################################################
        if (commandTest(CMD_BACKUP))
        {
            # Load module dynamically
            require pgBackRest::Backup::Backup;
            pgBackRest::Backup::Backup->import();

            new pgBackRest::Backup::Backup()->process();

            commandSet(CMD_EXPIRE);
        }

        ############################################################################################################################
        # EXPIRE
        ############################################################################################################################
        if (commandTest(CMD_EXPIRE))
        {
            # Load module dynamically
            require pgBackRest::Expire;
            pgBackRest::Expire->import();

            new pgBackRest::Expire()->process();
        }
    }

    lockRelease();
    exitSafe(0);

    # uncoverable statement - exit should happen above
    &log(ASSERT, 'execution reached invalid location in ' . __FILE__ . ', line ' . __LINE__);
    exit ERROR_ASSERT;                                              # uncoverable statement
}

####################################################################################################################################
# Check for errors
####################################################################################################################################
or do
{
    exitSafe(undef, $EVAL_ERROR);
};

# uncoverable statement - errors should be handled in the do block above
&log(ASSERT, 'execution reached invalid location in ' . __FILE__ . ', line ' . __LINE__);
exit ERROR_ASSERT;                                                  # uncoverable statement
