####################################################################################################################################
# CommonTest.pm - Common globals used for testing
####################################################################################################################################
package pgBackRestTest::Common::FileTest;

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

use Cwd qw(abs_path cwd);
use Exporter qw(import);
    our @EXPORT = qw();
use File::Basename qw(dirname);
use File::Copy qw(move);
use File::Path qw(remove_tree);
use IO::Select;
use IPC::Open3;
use POSIX ':sys_wait_h';
use Symbol 'gensym';

use pgBackRest::Common::Ini;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Common::Wait;
use pgBackRest::Config::Config;
use pgBackRest::Manifest;
use pgBackRest::Storage::Base;

use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::HostGroupTest;
use pgBackRestTest::Common::LogTest;
use pgBackRestTest::Common::RunTest;
use pgBackRestTest::Common::VmTest;
use pgBackRestTest::Env::Host::HostBaseTest;
use pgBackRestTest::Env::Host::HostBackupTest;
use pgBackRestTest::Env::Host::HostDbCommonTest;
use pgBackRestTest::Env::Host::HostDbTest;
use pgBackRestTest::Env::Host::HostS3Test;

####################################################################################################################################
# testLinkCreate
#
# Create a symlink
####################################################################################################################################
sub testLinkCreate
{
    my $strLink = shift;
    my $strDestination = shift;

    # Create the file
    symlink($strDestination, $strLink)
        or confess "unable to link ${strLink} to ${strDestination}";
}

push(@EXPORT, qw(testLinkCreate));

####################################################################################################################################
# testPathMode
#
# Set mode of an existing path.
####################################################################################################################################
sub testPathMode
{
    my $strPath = shift;
    my $strMode = shift;

    # Set the mode
    chmod(oct($strMode), $strPath)
        or confess 'unable to set mode ${strMode} for ${strPath}';
}

push(@EXPORT, qw(testPathMode));

####################################################################################################################################
# testPathRemove
#
# Remove a path and all subpaths.
####################################################################################################################################
sub testPathRemove
{
    my $strPath = shift;
    my $bSuppressError = shift;

    executeTest('rm -rf ' . $strPath, {bSuppressError => $bSuppressError});
}

push(@EXPORT, qw(testPathRemove));

####################################################################################################################################
# testFileCreate
#
# Create a file specifying content, mode, and time.
####################################################################################################################################
sub testFileCreate
{
    my $strFile = shift;
    my $strContent = shift;
    my $lTime = shift;
    my $strMode = shift;

    # Open the file and save strContent to it
    my $hFile = shift;

    open($hFile, '>', $strFile)
        or confess "unable to open ${strFile} for writing";

    if (defined($strContent) && $strContent ne '')
    {
        syswrite($hFile, $strContent)
            or confess "unable to write to ${strFile}: $!";
    }

    close($hFile);

    # Set the time
    if (defined($lTime))
    {
        utime($lTime, $lTime, $strFile)
            or confess 'unable to set time ${lTime} for ${strPath}';
    }

    # Set the mode
    chmod(oct(defined($strMode) ? $strMode : '0600'), $strFile)
        or confess 'unable to set mode ${strMode} for ${strFile}';
}

push(@EXPORT, qw(testFileCreate));

####################################################################################################################################
# testFileRemove
#
# Remove a file.
####################################################################################################################################
sub testFileRemove
{
    my $strFile = shift;

    unlink($strFile)
        or confess "unable to remove ${strFile}: $!";
}

push(@EXPORT, qw(testFileRemove));

####################################################################################################################################
# forceStorageMode - force mode on a file or path
####################################################################################################################################
sub forceStorageMode
{
    # Assign function parameters, defaults, and log debug info
    my
    (
        $strOperation,
        $oStorage,
        $strPathExp,
        $strMode,
        $bRecurse
    ) =
        logDebugParam
        (
            __PACKAGE__ . '::forceStorageMode', \@_,
            {name => 'oStorage'},
            {name => 'strPathExp'},
            {name => 'strMode'},
            {name => 'bRecurse', optional => true, default => false},
        );

    # Mode commands are ignored on S3
    if (!$oStorage->can('type') || $oStorage->type() ne STORAGE_S3)
    {
        executeTest('chmod ' . ($bRecurse ? '-R ' : '') . "${strMode} " . $oStorage->pathGet($strPathExp));
    }

    # Return from function and log return values if any
    return logDebugReturn($strOperation);
}

push(@EXPORT, qw(forceStorageMode));

####################################################################################################################################
# forceStorageMove - force move a directory or file
####################################################################################################################################
sub forceStorageMove
{
    # Assign function parameters, defaults, and log debug info
    my
    (
        $strOperation,
        $oStorage,
        $strSourcePathExp,
        $strDestinationPathExp,
        $bRecurse,
    ) =
        logDebugParam
        (
            __PACKAGE__ . '->forceStorageMove', \@_,
            {name => 'oStorage'},
            {name => 'strSourcePathExp'},
            {name => 'strDestinationPathExp'},
            {name => 'bRecurse', optional => true, default => true},
        );

    # If S3 then use storage commands to remove
    if ($oStorage->type() eq STORAGE_S3)
    {
        if ($bRecurse)
        {
            my $rhManifest = $oStorage->manifest($strSourcePathExp);

            foreach my $strName (sort(keys(%{$rhManifest})))
            {
                if ($rhManifest->{$strName}{type} eq 'f')
                {
                    $oStorage->put(
                        new pgBackRest::Storage::StorageWrite(
                            $oStorage,
                            pgBackRest::LibC::StorageWrite->new(
                                $oStorage->{oStorageC}, "${strDestinationPathExp}/${strName}", 0, undef, undef, 0, true, false)),
                        ${$oStorage->get(
                            new pgBackRest::Storage::StorageRead(
                                $oStorage,
                                pgBackRest::LibC::StorageRead->new(
                                    $oStorage->{oStorageC}, "${strSourcePathExp}/${strName}", false)))});

                    $oStorage->remove("${strSourcePathExp}/${strName}");
                }
            }
        }
        else
        {
            $oStorage->put($strDestinationPathExp, ${$oStorage->get($strSourcePathExp)});
            $oStorage->remove($strSourcePathExp);
        }
    }
    # Else remove using filesystem commands
    else
    {
        executeTest('mv ' . $oStorage->pathGet($strSourcePathExp) . ' ' . $oStorage->pathGet($strDestinationPathExp));
    }

    # Return from function and log return values if any
    return logDebugReturn($strOperation);
}

push(@EXPORT, qw(forceStorageMove));

####################################################################################################################################
# forceStorageOwner - force ownership on a file or path
####################################################################################################################################
sub forceStorageOwner
{
    # Assign function parameters, defaults, and log debug info
    my
    (
        $strOperation,
        $oStorage,
        $strPathExp,
        $strOwner,
        $bRecurse
    ) =
        logDebugParam
        (
            __PACKAGE__ . '::forceStorageOwner', \@_,
            {name => 'oStorage'},
            {name => 'strPathExp'},
            {name => 'strOwner'},
            {name => 'bRecurse', optional => true, default => false},
        );

    # Owner commands are ignored on S3
    if ($oStorage->type() ne STORAGE_S3)
    {
        executeTest('chown ' . ($bRecurse ? '-R ' : '') . "${strOwner} " . $oStorage->pathGet($strPathExp));
    }

    # Return from function and log return values if any
    return logDebugReturn($strOperation);
}

push(@EXPORT, qw(forceStorageOwner));

####################################################################################################################################
# forceStorageRemove - force remove a file or path from storage
####################################################################################################################################
sub forceStorageRemove
{
    # Assign function parameters, defaults, and log debug info
    my
    (
        $strOperation,
        $oStorage,
        $strPathExp,
        $bRecurse
    ) =
        logDebugParam
        (
            __PACKAGE__ . '->forceStorageRemove', \@_,
            {name => 'oStorage'},
            {name => 'strPathExp'},
            {name => 'bRecurse', optional => true, default => false},
        );

    # If S3 then use storage commands to remove
    if ($oStorage->type() eq STORAGE_S3)
    {
        my $oInfo = $oStorage->info($strPathExp, {bIgnoreMissing => true});

        if (defined($oInfo) && $oInfo->{type} eq 'f')
        {
            $oStorage->remove($strPathExp);
        }
        else
        {
            $oStorage->pathRemove($strPathExp, {bRecurse => true});
        }
    }
    else
    {
        executeTest('rm -f' . ($bRecurse ? 'r ' : ' ') . $oStorage->pathGet($strPathExp));
    }

    # Return from function and log return values if any
    return logDebugReturn($strOperation);
}

push(@EXPORT, qw(forceStorageRemove));

1;
