1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-03-03 14:52:21 +02:00

Add Perl interface to C storage layer.

Maintaining the storage layer/drivers in two languages is burdensome.  Since the integration tests require the Perl storage layer/drivers we'll need them even after the core code is migrated to C.  Create an interface layer so the Perl code can be removed and new storage drivers/features introduced without adding Perl equivalents.

The goal is to move the integration tests to C so this interface will eventually be removed.  That being the case, the interface was designed for maximum compatibility to ease the transition.  The result looks a bit hacky but we'll improve it as needed until it can be retired.
This commit is contained in:
David Steele 2019-06-26 08:24:58 -04:00
parent bd6c0941e9
commit 4815752ccc
93 changed files with 4412 additions and 12102 deletions

View File

@ -13,6 +13,17 @@
<release-list>
<release date="XXXX-XX-XX" version="2.16dev" title="UNDER DEVELOPMENT">
<release-core-list>
<release-development-list>
<release-item>
<release-item-contributor-list>
<release-item-reviewer id="cynthia.shang"/>
</release-item-contributor-list>
<p>Add Perl interface to C storage layer.</p>
</release-item>
</release-development-list>
</release-core-list>
</release>
<release date="2019-06-25" version="2.15" title="C Implementation of Expire">

View File

@ -584,20 +584,6 @@
<!-- ======================================================================================================================= -->
<block-define id="s3-setup">
<execute-list host="{[s3-setup-host]}">
<title>Install packages required for S3-compatible object store support</title>
<execute if="{[os-type-is-debian]}" user="root" pre="y">
<exe-cmd>apt-get install libio-socket-ssl-perl libxml-libxml-perl</exe-cmd>
<exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra>
</execute>
<execute if="{[os-type-is-centos]}" user="root" pre="y">
<exe-cmd>yum install perl-XML-LibXML perl-IO-Socket-SSL</exe-cmd>
<exe-cmd-extra>-y 2>&amp;1</exe-cmd-extra>
</execute>
</execute-list>
<p><backrest/> supports locating repositories in <proper>S3-compatible</proper> object stores. The bucket used to store the repository must be created in advance &amp;mdash; <backrest/> will not do it automatically. The repository can be located in the bucket root (<path>/</path>) but it's usually best to place it in a subpath so object store logs or other data can also be stored in the bucket without conflicts.</p>
<execute-list if="'{[s3-local]}' eq 'y'" host="{[s3-setup-host]}" show="n">
@ -627,7 +613,6 @@
<backrest-config-option section="global" key="repo1-s3-bucket">{[s3-bucket]}</backrest-config-option>
<backrest-config-option section="global" key="repo1-s3-endpoint">{[s3-endpoint]}</backrest-config-option>
<backrest-config-option section="global" key="repo1-s3-region">{[s3-region]}</backrest-config-option>
<backrest-config-option section="global" key="repo1-s3-ca-file" if="{[os-type-is-centos]}">/etc/pki/tls/certs/ca-bundle.crt</backrest-config-option>
<backrest-config-option section="global" key="process-max">4</backrest-config-option>
</backrest-config>

View File

@ -21,9 +21,6 @@ use pgBackRest::Common::Log;
use pgBackRest::Config::Config;
use pgBackRest::Protocol::Helper;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::Gzip;
use pgBackRest::Storage::Filter::Sha;
use pgBackRest::Storage::Helper;
####################################################################################################################################

View File

@ -27,7 +27,6 @@ use pgBackRest::InfoCommon;
use pgBackRest::Manifest;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::Gzip;
use pgBackRest::Storage::Helper;
####################################################################################################################################
@ -409,9 +408,6 @@ sub reconstruct
# Get the db-system-id from the WAL file depending on the version of postgres
my $iSysIdOffset = $strDbVersion >= PG_VERSION_93 ? PG_WAL_SYSTEM_ID_OFFSET_GTE_93 : PG_WAL_SYSTEM_ID_OFFSET_LT_93;
# Read first 8k of WAL segment
my $tBlock;
# Error if the file encryption setting is not valid for the repo
if (!storageRepo()->encryptionValid(storageRepo()->encrypted($strArchiveFilePath)))
{
@ -423,10 +419,12 @@ sub reconstruct
my $oFileIo = storageRepo()->openRead(
$strArchiveFilePath,
{rhyFilter => $strArchiveFile =~ ('\.' . COMPRESS_EXT . '$') ?
[{strClass => STORAGE_FILTER_GZIP, rxyParam => [{strCompressType => STORAGE_DECOMPRESS}]}] : undef,
[{strClass => STORAGE_FILTER_GZIP, rxyParam => [STORAGE_DECOMPRESS, false]}] : undef,
strCipherPass => $self->cipherPassSub()});
$oFileIo->open();
$oFileIo->read(\$tBlock, 512, true);
my $tBlock;
$oFileIo->read(\$tBlock, 512);
$oFileIo->close();
# Get the required data from the file that was pulled into scalar $tBlock

View File

@ -30,8 +30,6 @@ use pgBackRest::Protocol::Helper;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Common::Io::Handle;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::Gzip;
use pgBackRest::Storage::Filter::Sha;
use pgBackRest::Storage::Helper;
use pgBackRest::Version;
@ -229,7 +227,7 @@ sub resumeClean
if ($cType eq 'd')
{
logDebugMisc($strOperation, "remove path ${strName}");
$oStorageRepo->remove(STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strName}", {bRecurse => true});
$oStorageRepo->pathRemove(STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strName}", {bRecurse => true});
}
# Else add the file/link to be deleted later
else
@ -323,7 +321,7 @@ sub processManifest
storageRepo()->pathCreate(STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strPath}", {bIgnoreExists => true});
}
if (storageRepo()->driver()->capability(STORAGE_CAPABILITY_LINK))
if (storageRepo()->capability(STORAGE_CAPABILITY_LINK))
{
for my $strTarget ($oBackupManifest->keys(MANIFEST_SECTION_BACKUP_TARGET))
{
@ -745,7 +743,7 @@ sub process
&log(WARN, "aborted backup ${strAbortedBackup} cannot be resumed: ${strReason}");
&log(TEST, TEST_BACKUP_NORESUME);
$oStorageRepo->remove(STORAGE_REPO_BACKUP . "/${strAbortedBackup}", {bRecurse => true});
$oStorageRepo->pathRemove(STORAGE_REPO_BACKUP . "/${strAbortedBackup}", {bRecurse => true});
undef($oAbortedManifest);
}
@ -979,7 +977,9 @@ sub process
# Add compression filter
if ($bCompress)
{
push(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP});
push(
@{$rhyFilter},
{strClass => STORAGE_FILTER_GZIP, rxyParam => [STORAGE_COMPRESS, false, cfgOption(CFGOPT_COMPRESS_LEVEL)]});
}
# If the backups are encrypted, then the passphrase for the backup set from the manifest file is required to access
@ -1064,7 +1064,7 @@ sub process
$oBackupManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL, undef, $strBackupLabel);
# Sync backup path if supported
if ($oStorageRepo->driver()->capability(STORAGE_CAPABILITY_PATH_SYNC))
if ($oStorageRepo->capability(STORAGE_CAPABILITY_PATH_SYNC))
{
# Sync all paths in the backup
$oStorageRepo->pathSync(STORAGE_REPO_BACKUP . "/${strBackupLabel}");
@ -1096,12 +1096,12 @@ sub process
{'strCipherPass' => $strCipherPassManifest}),
$oStorageRepo->openWrite(
"${strHistoryPath}/${strBackupLabel}.manifest." . COMPRESS_EXT,
{rhyFilter => [{strClass => STORAGE_FILTER_GZIP}],
{rhyFilter => [{strClass => STORAGE_FILTER_GZIP, rxyParam => [STORAGE_COMPRESS, false, 9]}],
bPathCreate => true, bAtomic => true,
strCipherPass => defined($strCipherPassManifest) ? $strCipherPassManifest : undef}));
# Sync history path if supported
if ($oStorageRepo->driver()->capability(STORAGE_CAPABILITY_PATH_SYNC))
if ($oStorageRepo->capability(STORAGE_CAPABILITY_PATH_SYNC))
{
$oStorageRepo->pathSync(STORAGE_REPO_BACKUP . qw{/} . PATH_BACKUP_HISTORY);
$oStorageRepo->pathSync($strHistoryPath);
@ -1110,7 +1110,7 @@ sub process
# Create a link to the most recent backup
$oStorageRepo->remove(STORAGE_REPO_BACKUP . qw(/) . LINK_LATEST);
if (storageRepo()->driver()->capability(STORAGE_CAPABILITY_LINK))
if (storageRepo()->capability(STORAGE_CAPABILITY_LINK))
{
$oStorageRepo->linkCreate(
STORAGE_REPO_BACKUP . "/${strBackupLabel}", STORAGE_REPO_BACKUP . qw{/} . LINK_LATEST, {bRelative => true});
@ -1120,7 +1120,7 @@ sub process
$oBackupInfo->add($oBackupManifest);
# Sync backup root path if supported
if ($oStorageRepo->driver()->capability(STORAGE_CAPABILITY_PATH_SYNC))
if ($oStorageRepo->capability(STORAGE_CAPABILITY_PATH_SYNC))
{
$oStorageRepo->pathSync(STORAGE_REPO_BACKUP);
}

View File

@ -12,17 +12,15 @@ use Exporter qw(import);
use File::Basename qw(dirname);
use Storable qw(dclone);
use pgBackRest::Backup::Filter::PageChecksum;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Io::Handle;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Config::Config;
use pgBackRest::DbVersion;
use pgBackRest::Manifest;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::Gzip;
use pgBackRest::Storage::Filter::Sha;
use pgBackRest::Storage::Helper;
####################################################################################################################################
@ -143,7 +141,7 @@ sub backupFile
if ($bCompress)
{
push(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP, rxyParam => [{strCompressType => STORAGE_DECOMPRESS}]});
push(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP, rxyParam => [STORAGE_DECOMPRESS, false]});
}
# Get the checksum
@ -163,8 +161,8 @@ sub backupFile
# Copy the file
if ($bCopy)
{
# Add sha filter
my $rhyFilter = [{strClass => STORAGE_FILTER_SHA}];
# Add size and sha filters
my $rhyFilter = [{strClass => COMMON_IO_HANDLE}, {strClass => STORAGE_FILTER_SHA}];
# Add page checksum filter
if ($bChecksumPage)
@ -174,29 +172,44 @@ sub backupFile
push(
@{$rhyFilter},
{strClass => BACKUP_FILTER_PAGECHECKSUM,
{strClass => "pgBackRest::Backup::Filter::PageChecksum",
rxyParam => [$iSegmentNo, $hExtraParam->{iWalId}, $hExtraParam->{iWalOffset}]});
};
# Add compression
if ($bCompress)
{
push(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP, rxyParam => [{iLevel => $iCompressLevel}]});
push(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP, rxyParam => [STORAGE_COMPRESS, false, $iCompressLevel]});
}
# Else add protocol compression if the destination is not compressed and there is no encryption
elsif (!defined($strCipherPass))
{
push(
@{$rhyFilter},
{strClass => STORAGE_FILTER_GZIP, rxyParam => [STORAGE_COMPRESS, true, cfgOption(CFGOPT_COMPRESS_LEVEL)]});
}
# Open the file
# Open the source file
my $oSourceFileIo = storageDb()->openRead($strDbFile, {rhyFilter => $rhyFilter, bIgnoreMissing => $bIgnoreMissing});
# If source file exists
if (defined($oSourceFileIo))
# Open the destination file
$rhyFilter = undef;
# Add protocol decompression if the destination is not compressed and there is no encryption
if (!$bCompress && !defined($strCipherPass))
{
my $oDestinationFileIo = $oStorageRepo->openWrite(
STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strFileOp}",
{bPathCreate => true, bProtocolCompress => !$bCompress, strCipherPass => $strCipherPass});
push(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP, rxyParam => [STORAGE_DECOMPRESS, true]});
}
# Copy the file
$oStorageRepo->copy($oSourceFileIo, $oDestinationFileIo);
my $oDestinationFileIo = $oStorageRepo->openWrite(
STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strFileOp}",
{bPathCreate => true, rhyFilter => $rhyFilter, strCipherPass => $strCipherPass});
$oDestinationFileIo->{oStorageCWrite}->filterAdd(COMMON_IO_HANDLE, undef);
# Copy the file
if ($oStorageRepo->copy($oSourceFileIo, $oDestinationFileIo))
{
# Get sha checksum and size
$strCopyChecksum = $oSourceFileIo->result(STORAGE_FILTER_SHA);
$lCopySize = $oSourceFileIo->result(COMMON_IO_HANDLE);
@ -208,7 +221,17 @@ sub backupFile
}
# Get results of page checksum validation
$rExtra = $bChecksumPage ? $oSourceFileIo->result(BACKUP_FILTER_PAGECHECKSUM) : undef;
if ($bChecksumPage)
{
my $rExtraRaw = $oSourceFileIo->result("pgBackRest::Backup::Filter::PageChecksum");
$rExtra =
{
bValid => $rExtraRaw->{valid} ? true : false,
bAlign => $rExtraRaw->{align} ? true : false,
iyPageError => $rExtraRaw->{error},
};
}
}
# Else if source file is missing the database removed it
else
@ -223,21 +246,21 @@ sub backupFile
#
# If the file was checksummed then get the size in all cases since we don't already have it.
if ((($iCopyResult == BACKUP_FILE_COPY || $iCopyResult == BACKUP_FILE_RECOPY) &&
$oStorageRepo->driver()->capability(STORAGE_CAPABILITY_SIZE_DIFF)) ||
$oStorageRepo->capability(STORAGE_CAPABILITY_SIZE_DIFF)) ||
$iCopyResult == BACKUP_FILE_CHECKSUM)
{
$lRepoSize = ($oStorageRepo->info(STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strFileOp}"))->size();
$lRepoSize = ($oStorageRepo->info(STORAGE_REPO_BACKUP . "/${strBackupLabel}/${strFileOp}"))->{size};
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'iCopyResult', value => $iCopyResult, trace => true},
{name => 'lCopySize', value => $lCopySize, trace => true},
{name => 'lRepoSize', value => $lRepoSize, trace => true},
{name => 'strCopyChecksum', value => $strCopyChecksum, trace => true},
{name => 'rExtra', value => $rExtra, trace => true},
{name => 'iCopyResult', value => $iCopyResult},
{name => 'lCopySize', value => $lCopySize},
{name => 'lRepoSize', value => $lRepoSize},
{name => 'strCopyChecksum', value => $strCopyChecksum},
{name => 'rExtra', value => $rExtra},
);
}

View File

@ -1,168 +0,0 @@
####################################################################################################################################
# Backup Page Checksum Filter
####################################################################################################################################
package pgBackRest::Backup::Filter::PageChecksum;
use parent 'pgBackRest::Common::Io::Filter';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::DbVersion qw(PG_PAGE_SIZE);
use pgBackRest::LibC qw(:checksum);
####################################################################################################################################
# Package name constant
####################################################################################################################################
use constant BACKUP_FILTER_PAGECHECKSUM => __PACKAGE__;
push @EXPORT, qw(BACKUP_FILTER_PAGECHECKSUM);
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oParent,
$iSegmentNo,
$iWalId,
$iWalOffset,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oParent', trace => true},
{name => 'iSegmentNo', trace => true},
{name => 'iWalId', trace => true},
{name => 'iWalOffset', trace => true},
);
# Bless with new class
my $self = $class->SUPER::new($oParent);
bless $self, $class;
# Set variables
$self->{iSegmentNo} = $iSegmentNo;
$self->{iWalId} = $iWalId;
$self->{iWalOffset} = $iWalOffset;
# Create the result object
$self->{hResult}{bValid} = true;
$self->{hResult}{bAlign} = true;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# read - validate page checksums
####################################################################################################################################
sub read
{
my $self = shift;
my $rtBuffer = shift;
my $iSize = shift;
# Call the io method
my $iActualSize = $self->parent()->read($rtBuffer, $iSize);
# Validate page checksums for the read block
if ($iActualSize > 0)
{
# If the buffer is not divisible by 0 then it's not valid
if (!$self->{hResult}{bAlign} || ($iActualSize % PG_PAGE_SIZE != 0))
{
if (!$self->{hResult}{bAlign})
{
confess &log(ASSERT, "should not be possible to see two misaligned blocks in a row");
}
$self->{hResult}{bValid} = false;
$self->{hResult}{bAlign} = false;
delete($self->{hResult}{iyPageError});
}
else
{
# Calculate offset to the first block in the buffer
my $iBlockOffset = int(($self->size() - $iActualSize) / PG_PAGE_SIZE) + ($self->{iSegmentNo} * 131072);
if (!pageChecksumBufferTest(
$$rtBuffer, $iActualSize, $iBlockOffset, PG_PAGE_SIZE, $self->{iWalId},
$self->{iWalOffset}))
{
$self->{hResult}{bValid} = false;
# Now figure out exactly where the errors occurred. It would be more efficient if the checksum function returned an
# array, but we're hoping there won't be that many errors to scan so this should work fine.
for (my $iBlockNo = 0; $iBlockNo < int($iActualSize / PG_PAGE_SIZE); $iBlockNo++)
{
my $iBlockNoStart = $iBlockOffset + $iBlockNo;
if (!pageChecksumTest(
substr($$rtBuffer, $iBlockNo * PG_PAGE_SIZE, PG_PAGE_SIZE), $iBlockNoStart, PG_PAGE_SIZE,
$self->{iWalId}, $self->{iWalOffset}))
{
my $iLastIdx = defined($self->{hResult}{iyPageError}) ? @{$self->{hResult}{iyPageError}} - 1 : 0;
my $iyLast = defined($self->{hResult}{iyPageError}) ? $self->{hResult}{iyPageError}[$iLastIdx] : undef;
if (!defined($iyLast) || (!ref($iyLast) && $iyLast != $iBlockNoStart - 1) ||
(ref($iyLast) && $iyLast->[1] != $iBlockNoStart - 1))
{
push(@{$self->{hResult}{iyPageError}}, $iBlockNoStart);
}
elsif (!ref($iyLast))
{
$self->{hResult}{iyPageError}[$iLastIdx] = undef;
push(@{$self->{hResult}{iyPageError}[$iLastIdx]}, $iyLast);
push(@{$self->{hResult}{iyPageError}[$iLastIdx]}, $iBlockNoStart);
}
else
{
$self->{hResult}{iyPageError}[$iLastIdx][1] = $iBlockNoStart;
}
}
}
}
}
}
# Return the actual size read
return $iActualSize;
}
####################################################################################################################################
# close - close and set the result
####################################################################################################################################
sub close
{
my $self = shift;
if (defined($self->{hResult}))
{
# Set result
$self->resultSet(BACKUP_FILTER_PAGECHECKSUM, $self->{hResult});
# Delete the sha object
undef($self->{hResult});
# Close io
return $self->parent()->close();
}
}
1;

View File

@ -1,364 +0,0 @@
####################################################################################################################################
# HTTP Client
####################################################################################################################################
package pgBackRest::Common::Http::Client;
use parent 'pgBackRest::Common::Io::Buffered';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use IO::Socket::SSL;
use Socket qw(SOL_SOCKET SO_KEEPALIVE);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Io::Buffered;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Common::Xml;
use pgBackRest::Common::Http::Common;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant HTTP_VERB_GET => 'GET';
push @EXPORT, qw(HTTP_VERB_GET);
use constant HTTP_VERB_POST => 'POST';
push @EXPORT, qw(HTTP_VERB_POST);
use constant HTTP_VERB_PUT => 'PUT';
push @EXPORT, qw(HTTP_VERB_PUT);
use constant HTTP_HEADER_CONTENT_LENGTH => 'content-length';
push @EXPORT, qw(HTTP_HEADER_CONTENT_LENGTH);
use constant HTTP_HEADER_TRANSFER_ENCODING => 'transfer-encoding';
push @EXPORT, qw(HTTP_HEADER_TRANSFER_ENCODING);
####################################################################################################################################
# new
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strHost,
$strVerb,
$iPort,
$strUri,
$hQuery,
$hRequestHeader,
$rstrRequestBody,
$bResponseBodyPrefetch,
$iProtocolTimeout,
$iTryTotal,
$lBufferMax,
$bVerifySsl,
$strCaPath,
$strCaFile,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'strHost', trace => true},
{name => 'strVerb', trace => true},
{name => 'iPort', optional => true, default => 443, trace => true},
{name => 'strUri', optional => true, default => qw(/), trace => true},
{name => 'hQuery', optional => true, trace => true},
{name => 'hRequestHeader', optional => true, trace => true},
{name => 'rstrRequestBody', optional => true, trace => true},
{name => 'bResponseBodyPrefetch', optional => true, default => false, trace => true},
{name => 'iProtocolTimeout', optional => true, default => 300, trace => true},
{name => 'iTryTotal', optional => true, default => 3, trace => true},
{name => 'lBufferMax', optional => true, default => 32768, trace => true},
{name => 'bVerifySsl', optional => true, default => true, trace => true},
{name => 'strCaPath', optional => true, trace => true},
{name => 'strCaFile', optional => true, trace => true},
);
# Retry as many times as requested
my $self;
my $iTry = 1;
my $bRetry;
do
{
# Disable logging if a failure will be retried
logDisable() if $iTry < $iTryTotal;
$bRetry = false;
eval
{
# Connect to the server
my $oSocket;
if (eval{require IO::Socket::IP})
{
$oSocket = IO::Socket::IP->new(PeerHost => $strHost, PeerPort => $iPort)
or confess &log(ERROR, "unable to create socket: $@", ERROR_HOST_CONNECT);
}
else
{
require IO::Socket::INET;
$oSocket = IO::Socket::INET->new(PeerHost => $strHost, PeerPort => $iPort)
or confess &log(ERROR, "unable to create socket: $@", ERROR_HOST_CONNECT);
}
setsockopt($oSocket, SOL_SOCKET,SO_KEEPALIVE, 1)
or confess &log(ERROR, "unable to set socket keepalive: $@", ERROR_HOST_CONNECT);
eval
{
IO::Socket::SSL->start_SSL(
$oSocket, SSL_verify_mode => $bVerifySsl ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, SSL_ca_path => $strCaPath,
SSL_ca_file => $strCaFile);
}
or do
{
logErrorResult(
ERROR_HOST_CONNECT, coalesce(length($!) == 0 ? undef : $!, $SSL_ERROR), length($!) > 0 ? $SSL_ERROR : undef);
};
# Bless with new class
$self = $class->SUPER::new(
new pgBackRest::Common::Io::Handle('httpClient', $oSocket, $oSocket), $iProtocolTimeout, $lBufferMax);
bless $self, $class;
# Store socket
$self->{oSocket} = $oSocket;
# Generate the query string
my $strQuery = httpQuery($hQuery);
# Construct the request headers
$self->{strRequestHeader} = "${strVerb} " . httpUriEncode($strUri, true) . "?${strQuery} HTTP/1.1" . "\r\n";
foreach my $strHeader (sort(keys(%{$hRequestHeader})))
{
$self->{strRequestHeader} .= "${strHeader}: $hRequestHeader->{$strHeader}\r\n";
}
$self->{strRequestHeader} .= "\r\n";
# Write request headers
$self->write(\$self->{strRequestHeader});
# Write content
if (defined($rstrRequestBody))
{
my $iTotalSize = length($$rstrRequestBody);
my $iTotalSent = 0;
# Write the request body in buffer-sized chunks
do
{
my $strBufferWrite = substr($$rstrRequestBody, $iTotalSent, $lBufferMax);
$iTotalSent += $self->write(\$strBufferWrite);
} while ($iTotalSent < $iTotalSize);
}
# Read response code
($self->{strResponseProtocol}, $self->{iResponseCode}, $self->{strResponseMessage}) =
split(' ', trim($self->readLine()));
# Read the response headers
$self->{iContentLength} = 0;
$self->{strResponseHeader} = '';
my $strHeader = trim($self->readLine());
while ($strHeader ne '')
{
# Validate header
$self->{strResponseHeader} .= "${strHeader}\n";
my $iColonPos = index($strHeader, ':');
if ($iColonPos == -1)
{
confess &log(ERROR, "http header '${strHeader}' requires colon separator", ERROR_PROTOCOL);
}
# Parse header
my $strHeaderKey = lc(substr($strHeader, 0, $iColonPos));
my $strHeaderValue = trim(substr($strHeader, $iColonPos + 1));
# Store the header
$self->{hResponseHeader}{$strHeaderKey} = $strHeaderValue;
# Process content length
if ($strHeaderKey eq HTTP_HEADER_CONTENT_LENGTH)
{
$self->{iContentLength} = $strHeaderValue + 0;
$self->{iContentRemaining} = $self->{iContentLength};
}
# Process transfer encoding (only chunked is supported)
elsif ($strHeaderKey eq HTTP_HEADER_TRANSFER_ENCODING)
{
if ($strHeaderValue eq 'chunked')
{
$self->{iContentLength} = -1;
}
else
{
confess &log(ERROR, "invalid value '${strHeaderValue} for http header '${strHeaderKey}'", ERROR_PROTOCOL);
}
}
# Read next header
$strHeader = trim($self->readLine());
}
# Prefetch response - mostly useful when the response is known to be short
if ($bResponseBodyPrefetch)
{
$self->{strResponseBody} = $self->responseBody();
}
# Enable logging if a failure will be retried
logEnable() if $iTry < $iTryTotal;
return 1;
}
or do
{
# Enable logging if a failure will be retried
logEnable() if $iTry < $iTryTotal;
# If tries reaches total allowed then error
if ($iTry == $iTryTotal)
{
confess $EVAL_ERROR;
}
# Try again
$iTry++;
$bRetry = true;
};
}
while ($bRetry);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# read - read content from http stream
####################################################################################################################################
sub read
{
my $self = shift;
my $rtBuffer = shift;
my $iRequestSize = shift;
# Make sure request size is not larger than what remains to be read
$iRequestSize = $iRequestSize < $self->{iContentRemaining} ? $iRequestSize : $self->{iContentRemaining};
$self->{iContentRemaining} -= $iRequestSize;
my $iActualSize = $self->SUPER::read($rtBuffer, $iRequestSize, true);
# Set eof if there is nothing left to read
if ($self->{iContentRemaining} == 0)
{
$self->SUPER::eofSet(true);
}
return $iActualSize;
}
####################################################################################################################################
# close/DESTROY - close the HTTP connection
####################################################################################################################################
sub close
{
my $self = shift;
# Only close if the socket is open
if (defined($self->{oSocket}))
{
$self->{oSocket}->close();
undef($self->{oSocket});
}
}
sub DESTROY {shift->close()}
####################################################################################################################################
# responseBody - return the entire body of the response in a buffer
####################################################################################################################################
sub responseBody
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
) =
logDebugParam
(
__PACKAGE__ . '->responseBody'
);
# Return prefetched response body if it exists
return $self->{strResponseBody} if exists($self->{strResponseBody});
# Fetch response body if content length is not 0
my $strResponseBody = undef;
if ($self->{iContentLength} != 0)
{
# Transfer encoding is chunked
if ($self->{iContentLength} == -1)
{
while (1)
{
# Read chunk length
my $strChunkLength = trim($self->readLine());
my $iChunkLength = hex($strChunkLength);
# Exit if chunk length is 0
last if ($iChunkLength == 0);
# Read the chunk and consume the terminating LF
$self->SUPER::read(\$strResponseBody, $iChunkLength, true);
$self->readLine();
};
}
# Else content length is known
else
{
$self->SUPER::read(\$strResponseBody, $self->{iContentLength}, true);
}
$self->close();
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'rstrResponseBody', value => \$strResponseBody, trace => true}
);
}
####################################################################################################################################
# Properties.
####################################################################################################################################
sub contentLength {shift->{iContentLength}} # Content length if available (-1 means not known yet)
sub requestHeaderText {trim(shift->{strRequestHeader})}
sub responseCode {shift->{iResponseCode}}
sub responseHeader {shift->{hResponseHeader}}
sub responseHeaderText {trim(shift->{strResponseHeader})}
sub responseMessage {shift->{strResponseMessage}}
sub responseProtocol {shift->{strResponseProtocol}}
1;

View File

@ -1,107 +0,0 @@
####################################################################################################################################
# HTTP Common
####################################################################################################################################
package pgBackRest::Common::Http::Common;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
####################################################################################################################################
# httpQuery - encode an HTTP query from a hash
####################################################################################################################################
sub httpQuery
{
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$hQuery,
) =
logDebugParam
(
__PACKAGE__ . '::httpQuery', \@_,
{name => 'hQuery', required => false, trace => true},
);
# Generate the query string
my $strQuery = '';
# If a hash (the normal case)
if (ref($hQuery))
{
foreach my $strParam (sort(keys(%{$hQuery})))
{
# Parameters may not be defined - this is OK
if (defined($hQuery->{$strParam}))
{
$strQuery .= ($strQuery eq '' ? '' : '&') . $strParam . '=' . httpUriEncode($hQuery->{$strParam});
}
}
}
# Else query string was passed directly as a scalar
elsif (defined($hQuery))
{
$strQuery = $hQuery;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strQuery', value => $strQuery, trace => true}
);
}
push @EXPORT, qw(httpQuery);
####################################################################################################################################
# httpUriEncode - encode query values to conform with URI specs
####################################################################################################################################
sub httpUriEncode
{
my $strString = shift;
my $bPath = shift;
# Only encode if source string is defined
my $strEncodedString;
if (defined($strString))
{
# Iterate all characters in the string
for (my $iIndex = 0; $iIndex < length($strString); $iIndex++)
{
my $cChar = substr($strString, $iIndex, 1);
# These characters are reproduced verbatim
if (($cChar ge 'A' && $cChar le 'Z') || ($cChar ge 'a' && $cChar le 'z') || ($cChar ge '0' && $cChar le '9') ||
$cChar eq '_' || $cChar eq '-' || $cChar eq '~' || $cChar eq '.' || ($bPath && $cChar eq '/'))
{
$strEncodedString .= $cChar;
}
# Forward slash is encoded
elsif ($cChar eq '/')
{
$strEncodedString .= '%2F';
}
# All other characters are hex-encoded
else
{
$strEncodedString .= sprintf('%%%02X', ord($cChar));
}
}
}
return $strEncodedString;
}
push @EXPORT, qw(httpUriEncode);
1;

View File

@ -1,163 +0,0 @@
####################################################################################################################################
# XML Helper Functions
####################################################################################################################################
package pgBackRest::Common::Xml;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use XML::LibXML;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
####################################################################################################################################
# xmlParse - parse a string into an xml document and return the root node
####################################################################################################################################
use constant XML_HEADER => '<?xml version="1.0" encoding="UTF-8"?>';
push @EXPORT, qw(XML_HEADER);
####################################################################################################################################
# Convert a string to xml so that it is suitable to be appended into xml
####################################################################################################################################
sub xmlFromText
{
my $strText = shift;
return XML::LibXML::Text->new($strText)->toString();
}
push @EXPORT, qw(xmlFromText);
####################################################################################################################################
# xmlParse - parse a string into an xml document and return the root node
####################################################################################################################################
sub xmlParse
{
my $rstrXml = shift;
my $oXml = XML::LibXML->load_xml(string => $rstrXml)->documentElement();
return $oXml;
}
push @EXPORT, qw(xmlParse);
####################################################################################################################################
# xmlTagChildren - get all children that match the tag
####################################################################################################################################
sub xmlTagChildren
{
my $oXml = shift;
my $strTag = shift;
return $oXml->getChildrenByTagName($strTag);
}
push @EXPORT, qw(xmlTagChildren);
####################################################################################################################################
# xmlTagText - get the text content for a tag, error if the tag is required and does not exist
####################################################################################################################################
sub xmlTagText
{
my $oXml = shift;
my $strTag = shift;
my $bRequired = shift;
# my $strDefault = shift;
# Get the tag or tags
my @oyTag = $oXml->getElementsByTagName($strTag);
# Error if the tag does not exist and is required
if (@oyTag > 1)
{
confess &log(ERROR, @oyTag . " '${strTag}' tag(s) exist, but only one was expected", ERROR_FORMAT);
}
elsif (@oyTag == 0)
{
if (!defined($bRequired) || $bRequired)
{
confess &log(ERROR, "tag '${strTag}' does not exist", ERROR_FORMAT);
}
}
else
{
return $oyTag[0]->textContent();
}
return;
}
push @EXPORT, qw(xmlTagText);
####################################################################################################################################
# xmlTagBool - get the boolean content for a tag, error if the tag is required and does not exist or is not boolean
####################################################################################################################################
sub xmlTagBool
{
my $oXml = shift;
my $strTag = shift;
my $bRequired = shift;
# my $strDefault = shift;
# Test content for boolean value
my $strContent = xmlTagText($oXml, $strTag, $bRequired);
if (defined($strContent))
{
if ($strContent eq 'true')
{
return true;
}
elsif ($strContent eq 'false')
{
return false;
}
else
{
confess &log(ERROR, "invalid boolean value '${strContent}' for tag '${strTag}'", ERROR_FORMAT);
}
}
return;
}
push @EXPORT, qw(xmlTagBool);
####################################################################################################################################
# xmlTagInt - get the integer content for a tag, error if the tag is required and does not exist or is not an integer
####################################################################################################################################
sub xmlTagInt
{
my $oXml = shift;
my $strTag = shift;
my $bRequired = shift;
# my $strDefault = shift;
# Test content for boolean value
my $iContent = xmlTagText($oXml, $strTag, $bRequired);
if (defined($iContent))
{
eval
{
$iContent = $iContent + 0;
return 1;
}
or do
{
confess &log(ERROR, "invalid integer value '${iContent}' for tag '${strTag}'", ERROR_FORMAT);
}
}
return $iContent;
}
push @EXPORT, qw(xmlTagInt);
1;

View File

@ -74,8 +74,6 @@ sub libcAutoExportTag
checksum =>
[
'pageChecksum',
'pageChecksumBufferTest',
'pageChecksumTest',
],
config =>
@ -352,7 +350,7 @@ sub libcAutoExportTag
storage =>
[
'storagePosixPathRemove',
'storageRepoFree',
],
test =>

View File

@ -11,7 +11,6 @@ use Carp qw(confess);
use pgBackRest::Backup::File;
use pgBackRest::Common::Log;
use pgBackRest::Config::Config;
use pgBackRest::Storage::Local;
use pgBackRest::Protocol::Base::Master;
use pgBackRest::Protocol::Base::Minion;
use pgBackRest::Protocol::Command::Minion;

View File

@ -91,11 +91,12 @@ sub init
my $oSourceFileIo = $oStorage->openRead(@{shift()});
# If the source file exists
if (defined($oSourceFileIo))
if (defined($oSourceFileIo) && (!defined($oSourceFileIo->{oStorageCRead}) || $oSourceFileIo->open()))
{
$self->outputWrite(true);
$oStorage->copy($oSourceFileIo, new pgBackRest::Protocol::Storage::File($self, $oSourceFileIo));
$oStorage->copy(
$oSourceFileIo, new pgBackRest::Protocol::Storage::File($self, $oSourceFileIo), {bSourceOpen => true});
return true;
}

View File

@ -13,11 +13,10 @@ use File::Basename qw(basename);
use pgBackRest::Common::Log;
use pgBackRest::Config::Config;
use pgBackRest::LibC qw(:storage);
use pgBackRest::Protocol::Helper;
use pgBackRest::Protocol::Storage::Remote;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Helper;
use pgBackRest::Storage::Local;
####################################################################################################################################
# Storage constants
@ -60,9 +59,8 @@ sub storageDb
{
if (isDbLocal({iRemoteIdx => $iRemoteIdx}))
{
$hStorage->{&STORAGE_DB}{$iRemoteIdx} = new pgBackRest::Storage::Local(
cfgOption(cfgOptionIdFromIndex(CFGOPT_PG_PATH, $iRemoteIdx)), new pgBackRest::Storage::Posix::Driver(),
{strTempExtension => STORAGE_TEMP_EXT, lBufferMax => cfgOption(CFGOPT_BUFFER_SIZE)});
$hStorage->{&STORAGE_DB}{$iRemoteIdx} = new pgBackRest::Storage::Storage(
STORAGE_DB, {lBufferMax => cfgOption(CFGOPT_BUFFER_SIZE)});
}
else
{
@ -81,54 +79,6 @@ sub storageDb
push @EXPORT, qw(storageDb);
####################################################################################################################################
# storageRepoRule - rules for paths in the repository
####################################################################################################################################
sub storageRepoRule
{
my $strRule = shift;
my $strFile = shift;
my $strStanza = shift;
# Result path and file
my $strResultFile;
# Return archive path
if ($strRule eq STORAGE_REPO_ARCHIVE)
{
$strResultFile = "archive" . (defined($strStanza) ? "/${strStanza}" : '');
# If file is not defined nothing further to do
if (defined($strFile))
{
my ($strArchiveId, $strWalFile) = split('/', $strFile);
# If a WAL file (including .backup)
if (defined($strWalFile) && $strWalFile =~ /^[0-F]{24}/)
{
$strResultFile .= "/${strArchiveId}/" . substr($strWalFile, 0, 16) . "/${strWalFile}";
}
# Else other files
else
{
$strResultFile .= "/${strFile}";
}
}
}
# Return backup path
elsif ($strRule eq STORAGE_REPO_BACKUP)
{
$strResultFile = "backup" . (defined($strStanza) ? "/${strStanza}" : '') . (defined($strFile) ? "/${strFile}" : '');
}
# Else error
else
{
confess &log(ASSERT, "invalid " . STORAGE_REPO . " storage rule ${strRule}");
}
return $strResultFile;
}
####################################################################################################################################
# storageRepo - get repository storage
####################################################################################################################################
@ -146,85 +96,18 @@ sub storageRepo
{name => 'strStanza', optional => true, trace => true},
);
if (!defined($strStanza))
{
if (cfgOptionValid(CFGOPT_STANZA) && cfgOptionTest(CFGOPT_STANZA))
{
$strStanza = cfgOption(CFGOPT_STANZA);
}
else
{
$strStanza = STORAGE_REPO;
}
}
# Create storage if not defined
if (!defined($hStorage->{&STORAGE_REPO}{$strStanza}))
if (!defined($hStorage->{&STORAGE_REPO}))
{
if (isRepoLocal())
{
# Path rules
my $hRule =
{
&STORAGE_REPO_ARCHIVE =>
{
fnRule => \&storageRepoRule,
xData => $strStanza eq STORAGE_REPO ? undef : $strStanza,
},
&STORAGE_REPO_BACKUP =>
{
fnRule => \&storageRepoRule,
xData => $strStanza eq STORAGE_REPO ? undef : $strStanza,
},
};
# Create the driver
my $oDriver;
if (cfgOptionTest(CFGOPT_REPO_TYPE, CFGOPTVAL_REPO_TYPE_S3))
{
require pgBackRest::Storage::S3::Driver;
$oDriver = new pgBackRest::Storage::S3::Driver(
cfgOption(CFGOPT_REPO_S3_BUCKET), cfgOption(CFGOPT_REPO_S3_ENDPOINT), cfgOption(CFGOPT_REPO_S3_REGION),
cfgOption(CFGOPT_REPO_S3_KEY), cfgOption(CFGOPT_REPO_S3_KEY_SECRET),
{strHost => cfgOption(CFGOPT_REPO_S3_HOST, false), bVerifySsl => cfgOption(CFGOPT_REPO_S3_VERIFY_TLS, false),
strCaPath => cfgOption(CFGOPT_REPO_S3_CA_PATH, false),
strCaFile => cfgOption(CFGOPT_REPO_S3_CA_FILE, false), lBufferMax => cfgOption(CFGOPT_BUFFER_SIZE),
strSecurityToken => cfgOption(CFGOPT_REPO_S3_TOKEN, false)});
}
elsif (cfgOptionTest(CFGOPT_REPO_TYPE, CFGOPTVAL_REPO_TYPE_CIFS))
{
require pgBackRest::Storage::Cifs::Driver;
$oDriver = new pgBackRest::Storage::Cifs::Driver();
}
else
{
$oDriver = new pgBackRest::Storage::Posix::Driver();
}
# Set the encryption for the repo
my $strCipherType;
my $strCipherPass;
# If the encryption is not the default (none) then set the user-defined passphrase and magic based on the type
if (cfgOption(CFGOPT_REPO_CIPHER_TYPE) ne CFGOPTVAL_REPO_CIPHER_TYPE_NONE)
{
$strCipherType = cfgOption(CFGOPT_REPO_CIPHER_TYPE);
$strCipherPass = cfgOption(CFGOPT_REPO_CIPHER_PASS);
}
# Create local storage
$hStorage->{&STORAGE_REPO}{$strStanza} = new pgBackRest::Storage::Local(
cfgOption(CFGOPT_REPO_PATH), $oDriver,
{strTempExtension => STORAGE_TEMP_EXT, hRule => $hRule, lBufferMax => cfgOption(CFGOPT_BUFFER_SIZE),
strCipherType => $strCipherType, strCipherPassUser => $strCipherPass});
$hStorage->{&STORAGE_REPO} = new pgBackRest::Storage::Storage(
STORAGE_REPO, {lBufferMax => cfgOption(CFGOPT_BUFFER_SIZE)});
}
else
{
# Create remote storage
$hStorage->{&STORAGE_REPO}{$strStanza} = new pgBackRest::Protocol::Storage::Remote(
$hStorage->{&STORAGE_REPO} = new pgBackRest::Protocol::Storage::Remote(
protocolGet(CFGOPTVAL_REMOTE_TYPE_BACKUP));
}
}
@ -233,7 +116,7 @@ sub storageRepo
return logDebugReturn
(
$strOperation,
{name => 'oStorageRepo', value => $hStorage->{&STORAGE_REPO}{$strStanza}, trace => true},
{name => 'oStorageRepo', value => $hStorage->{&STORAGE_REPO}, trace => true},
);
}
@ -249,6 +132,8 @@ sub storageRepoCacheClear
delete($hStorage->{&STORAGE_REPO});
storageRepoFree();
# Return from function and log return values if any
return logDebugReturn($strOperation);
}

View File

@ -15,7 +15,6 @@ use pgBackRest::Config::Config;
use pgBackRest::Protocol::Helper;
use pgBackRest::Protocol::Storage::File;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::Gzip;
####################################################################################################################################
# new
@ -195,29 +194,10 @@ sub openRead
{name => 'rhParam', required => false},
);
# Determine whether protocol compress will be used
my $bProtocolCompress = protocolCompress($rhParam);
# Compress on the remote side
if ($bProtocolCompress)
{
push(
@{$rhParam->{rhyFilter}},
{strClass => STORAGE_FILTER_GZIP,
rxyParam => [{iLevel => cfgOption(CFGOPT_COMPRESS_LEVEL_NETWORK), bWantGzip => false}]});
}
my $oSourceFileIo =
$self->{oProtocol}->cmdExecute(OP_STORAGE_OPEN_READ, [$strFileExp, $rhParam]) ?
new pgBackRest::Protocol::Storage::File($self->{oProtocol}) : undef;
# Decompress on the local side
if ($bProtocolCompress)
{
$oSourceFileIo = new pgBackRest::Storage::Filter::Gzip(
$oSourceFileIo, {strCompressType => STORAGE_DECOMPRESS, bWantGzip => false});
}
# Return from function and log return values if any
return logDebugReturn
(
@ -247,28 +227,10 @@ sub openWrite
{name => 'rhParam', required => false},
);
# Determine whether protocol compress will be used
my $bProtocolCompress = protocolCompress($rhParam);
# Decompress on the remote side
if ($bProtocolCompress)
{
push(
@{$rhParam->{rhyFilter}},
{strClass => STORAGE_FILTER_GZIP, rxyParam => [{strCompressType => STORAGE_DECOMPRESS, bWantGzip => false}]});
}
# Open the remote file
$self->{oProtocol}->cmdWrite(OP_STORAGE_OPEN_WRITE, [$strFileExp, $rhParam]);
my $oDestinationFileIo = new pgBackRest::Protocol::Storage::File($self->{oProtocol});
# Compress on local side
if ($bProtocolCompress)
{
$oDestinationFileIo = new pgBackRest::Storage::Filter::Gzip(
$oDestinationFileIo, {iLevel => cfgOption(CFGOPT_COMPRESS_LEVEL_NETWORK), bWantGzip => false});
}
# Return from function and log return values if any
return logDebugReturn
(
@ -367,24 +329,6 @@ sub cipherPassUser
);
}
####################################################################################################################################
# Used internally to determine if protocol compression should be enabled
####################################################################################################################################
sub protocolCompress
{
my $rhParam = shift;
my $bProtocolCompress = false;
if (defined($rhParam->{bProtocolCompress}))
{
$bProtocolCompress = $rhParam->{bProtocolCompress} && cfgOption(CFGOPT_COMPRESS_LEVEL_NETWORK) > 0 ? true : false;
delete($rhParam->{bProtocolCompress});
}
return $bProtocolCompress;
}
####################################################################################################################################
# getters
####################################################################################################################################

View File

@ -227,8 +227,7 @@ sub manifestLoad
# Copy the backup manifest to the db cluster path
storageDb()->copy(
storageRepo()->openRead(STORAGE_REPO_BACKUP . "/$self->{strBackupSet}/" . FILE_MANIFEST, {bProtocolCompress => true,
strCipherPass => $strCipherPass}),
storageRepo()->openRead(STORAGE_REPO_BACKUP . "/$self->{strBackupSet}/" . FILE_MANIFEST, {strCipherPass => $strCipherPass}),
$self->{strDbClusterPath} . '/' . FILE_MANIFEST);
# Load the manifest into a hash
@ -1089,7 +1088,7 @@ sub process
# Copy backup info, load it, then delete
$oStorageDb->copy(
storageRepo()->openRead(STORAGE_REPO_BACKUP . qw(/) . FILE_BACKUP_INFO, {bProtocolCompress => true}),
storageRepo()->openRead(STORAGE_REPO_BACKUP . qw(/) . FILE_BACKUP_INFO),
$self->{strDbClusterPath} . '/' . FILE_BACKUP_INFO);
my $oBackupInfo = new pgBackRest::Backup::Info($self->{strDbClusterPath}, false, undef, {oStorage => storageDb()});

View File

@ -20,9 +20,6 @@ use pgBackRest::Common::String;
use pgBackRest::Config::Config;
use pgBackRest::Manifest;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::Gzip;
use pgBackRest::Storage::Filter::Sha;
use pgBackRest::Storage::Helper;
####################################################################################################################################

View File

@ -294,7 +294,7 @@ sub stanzaDelete
my ($strOperation) = logDebugParam(__PACKAGE__ . '->stanzaDelete');
my $strStanza = cfgOption(CFGOPT_STANZA);
my $oStorageRepo = storageRepo({strStanza => $strStanza});
my $oStorageRepo = storageRepo();
# If at least an archive or backup directory exists for the stanza, then continue, else nothing to do
if ($oStorageRepo->pathExists(STORAGE_REPO_ARCHIVE) || $oStorageRepo->pathExists(STORAGE_REPO_BACKUP))
@ -342,8 +342,8 @@ sub stanzaDelete
}
# Recursively remove the stanza archive and backup directories
$oStorageRepo->remove(STORAGE_REPO_ARCHIVE, {bRecurse => true, bIgnoreMissing => true});
$oStorageRepo->remove(STORAGE_REPO_BACKUP, {bRecurse => true, bIgnoreMissing => true});
$oStorageRepo->pathRemove(STORAGE_REPO_ARCHIVE, {bRecurse => true, bIgnoreMissing => true});
$oStorageRepo->pathRemove(STORAGE_REPO_BACKUP, {bRecurse => true, bIgnoreMissing => true});
# Remove the stop file so processes can run.
lockStart();

View File

@ -16,6 +16,17 @@ use pgBackRest::Common::Exception;
use pgBackRest::Common::Io::Base;
use pgBackRest::Common::Log;
####################################################################################################################################
# Storage constants
####################################################################################################################################
use constant STORAGE_LOCAL => '<LOCAL>';
push @EXPORT, qw(STORAGE_LOCAL);
use constant STORAGE_S3 => 's3';
push @EXPORT, qw(STORAGE_S3);
use constant STORAGE_POSIX => 'posix';
push @EXPORT, qw(STORAGE_POSIX);
####################################################################################################################################
# Compress constants
####################################################################################################################################
@ -34,6 +45,16 @@ use constant STORAGE_DECRYPT => 'decrypt'
use constant CIPHER_MAGIC => 'Salted__';
push @EXPORT, qw(CIPHER_MAGIC);
####################################################################################################################################
# Filter constants
####################################################################################################################################
use constant STORAGE_FILTER_CIPHER_BLOCK => 'pgBackRest::Storage::Filter::CipherBlock';
push @EXPORT, qw(STORAGE_FILTER_CIPHER_BLOCK);
use constant STORAGE_FILTER_GZIP => 'pgBackRest::Storage::Filter::Gzip';
push @EXPORT, qw(STORAGE_FILTER_GZIP);
use constant STORAGE_FILTER_SHA => 'pgBackRest::Storage::Filter::Sha';
push @EXPORT, qw(STORAGE_FILTER_SHA);
####################################################################################################################################
# Capability constants
####################################################################################################################################
@ -78,9 +99,10 @@ sub new
);
}
####################################################################################################################################
# copy - copy a file. If special encryption settings are required, then the file objects from openRead/openWrite must be passed
# instead of file names.
# Copy a file. If special encryption settings are required, then the file objects from openRead/openWrite must be passed instead of
# file names.
####################################################################################################################################
sub copy
{
@ -92,47 +114,62 @@ sub copy
$strOperation,
$xSourceFile,
$xDestinationFile,
$bSourceOpen,
) =
logDebugParam
(
__PACKAGE__ . '->copy', \@_,
{name => 'xSourceFile', required => false},
{name => 'xDestinationFile', required => false},
{name => 'xDestinationFile'},
{name => 'bSourceOpen', optional => true, default => false},
);
# Was the file copied?
# Is source/destination an IO object or a file expression?
my $oSourceFileIo = defined($xSourceFile) ? (ref($xSourceFile) ? $xSourceFile : $self->openRead($xSourceFile)) : undef;
# Does the source file exist?
my $bResult = false;
# Is source an IO object or a file expression?
my $oSourceFileIo =
defined($xSourceFile) ?
(ref($xSourceFile) ? $xSourceFile : $self->openRead($self->pathGet($xSourceFile))) : undef;
# Proceed if source file exists
# Copy if the source file exists
if (defined($oSourceFileIo))
{
# Is destination an IO object or a file expression?
my $oDestinationFileIo = ref($xDestinationFile) ? $xDestinationFile : $self->openWrite($self->pathGet($xDestinationFile));
my $oDestinationFileIo = ref($xDestinationFile) ? $xDestinationFile : $self->openWrite($xDestinationFile);
# Copy the data
my $lSizeRead;
do
# Use C copy if source and destination are C objects
if (defined($oSourceFileIo->{oStorageCRead}) && defined($oDestinationFileIo->{oStorageCWrite}))
{
# Read data
my $tBuffer = '';
$lSizeRead = $oSourceFileIo->read(\$tBuffer, $self->{lBufferMax});
$oDestinationFileIo->write(\$tBuffer);
$bResult = $self->{oStorageC}->copy(
$oSourceFileIo->{oStorageCRead}, $oDestinationFileIo->{oStorageCWrite}) ? true : false;
}
while ($lSizeRead != 0);
else
{
# Open the source file if it is a C object
$bResult = defined($oSourceFileIo->{oStorageCRead}) ? ($bSourceOpen || $oSourceFileIo->open()) : true;
# Close files
$oSourceFileIo->close();
$oDestinationFileIo->close();
if ($bResult)
{
# Open the destination file if it is a C object
if (defined($oDestinationFileIo->{oStorageCWrite}))
{
$oDestinationFileIo->open();
}
# File was copied
$bResult = true;
# Copy the data
do
{
# Read data
my $tBuffer = '';
$oSourceFileIo->read(\$tBuffer, $self->{lBufferMax});
$oDestinationFileIo->write(\$tBuffer);
}
while (!$oSourceFileIo->eof());
# Close files
$oSourceFileIo->close();
$oDestinationFileIo->close();
}
}
}
return logDebugReturn

View File

@ -1,55 +0,0 @@
####################################################################################################################################
# CIFS Storage
#
# Implements storage functions for Posix-compliant file systems.
####################################################################################################################################
package pgBackRest::Storage::Cifs::Driver;
use parent 'pgBackRest::Storage::Posix::Driver';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use pgBackRest::Common::Log;
use pgBackRest::Storage::Base;
####################################################################################################################################
# Package name constant
####################################################################################################################################
use constant STORAGE_CIFS_DRIVER => __PACKAGE__;
push @EXPORT, qw(STORAGE_CIFS_DRIVER);
####################################################################################################################################
# pathSync - CIFS does not support path sync so this is a noop
####################################################################################################################################
sub pathSync
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
) =
logDebugParam
(
__PACKAGE__ . '->pathSync', \@_,
{name => 'strPath', trace => true},
);
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# Getters/Setters
####################################################################################################################################
sub capability {shift eq STORAGE_CAPABILITY_SIZE_DIFF ? true : false}
sub className {STORAGE_CIFS_DRIVER}
1;

View File

@ -1,177 +0,0 @@
####################################################################################################################################
# Block Cipher Filter
####################################################################################################################################
package pgBackRest::Storage::Filter::CipherBlock;
use parent 'pgBackRest::Common::Io::Filter';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use pgBackRest::Common::Exception;
use pgBackRest::Common::Io::Base;
use pgBackRest::Common::Log;
use pgBackRest::LibC qw(:crypto);
use pgBackRest::Storage::Base;
####################################################################################################################################
# Package name constant
####################################################################################################################################
use constant STORAGE_FILTER_CIPHER_BLOCK => __PACKAGE__;
push @EXPORT, qw(STORAGE_FILTER_CIPHER_BLOCK);
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oParent,
$strCipherType,
$tCipherPass,
$strMode,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oParent', trace => true},
{name => 'strCipherType', trace => true},
{name => 'tCipherPass', trace => true},
{name => 'strMode', optional => true, default => STORAGE_ENCRYPT, trace => true},
);
# Bless with new class
my $self = $class->SUPER::new($oParent);
bless $self, $class;
# Check mode is valid
$self->{strMode} = $strMode;
if (!($self->{strMode} eq STORAGE_ENCRYPT || $self->{strMode} eq STORAGE_DECRYPT))
{
confess &log(ASSERT, "unknown cipher mode: $self->{strMode}");
}
# Set read/write
$self->{bWrite} = false;
# Create cipher object
$self->{oCipher} = new pgBackRest::LibC::Cipher::Block(
$self->{strMode} eq STORAGE_ENCRYPT ? CIPHER_MODE_ENCRYPT : CIPHER_MODE_DECRYPT, $strCipherType, $tCipherPass,
length($tCipherPass));
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# read - encrypt/decrypt data
####################################################################################################################################
sub read
{
my $self = shift;
my $rtBuffer = shift;
my $iSize = shift;
# Return 0 if all data has been read
return 0 if $self->eof();
# Loop until required bytes have been read
my $tBufferRead = '';
my $iBufferReadSize = 0;
do
{
# Read data
my $tCipherBuffer;
my $iActualSize = $self->SUPER::read(\$tCipherBuffer, $iSize);
# If something was read, then process it
if ($iActualSize > 0)
{
$tBufferRead .= $self->{oCipher}->process($tCipherBuffer);
}
# If eof then flush the remaining data
if ($self->eof())
{
$tBufferRead .= $self->{oCipher}->flush();
}
# Get the current size of the read buffer
$iBufferReadSize = length($tBufferRead);
}
while ($iBufferReadSize < $iSize && !$self->eof());
# Append to the read buffer
$$rtBuffer .= $tBufferRead;
# Return the actual size read
return $iBufferReadSize;
}
####################################################################################################################################
# write - encrypt/decrypt data
####################################################################################################################################
sub write
{
my $self = shift;
my $rtBuffer = shift;
# Set write flag so close will flush buffer
$self->{bWrite} = true;
# Write the buffer if defined
my $tCipherBuffer;
if (defined($$rtBuffer))
{
$tCipherBuffer = $self->{oCipher}->process($$rtBuffer);
}
# Call the io method. If $rtBuffer is undefined, then this is expected to error.
$self->SUPER::write(\$tCipherBuffer);
return length($$rtBuffer);
}
####################################################################################################################################
# close - close the file
####################################################################################################################################
sub close
{
my $self = shift;
# Only close the object if not already closed
if ($self->{oCipher})
{
# Flush the write buffer
if ($self->{bWrite})
{
my $tCipherBuffer = $self->{oCipher}->flush();
$self->SUPER::write(\$tCipherBuffer);
}
undef($self->{oCipher});
# Close io
return $self->SUPER::close();
}
return false;
}
1;

View File

@ -1,264 +0,0 @@
####################################################################################################################################
# GZIP Filter
####################################################################################################################################
package pgBackRest::Storage::Filter::Gzip;
use parent 'pgBackRest::Common::Io::Filter';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Compress::Raw::Zlib qw(WANT_GZIP MAX_WBITS Z_OK Z_BUF_ERROR Z_DATA_ERROR Z_STREAM_END);
use Exporter qw(import);
our @EXPORT = qw();
use pgBackRest::Common::Exception;
use pgBackRest::Common::Io::Base;
use pgBackRest::Common::Log;
use pgBackRest::Storage::Base;
####################################################################################################################################
# Package name constant
####################################################################################################################################
use constant STORAGE_FILTER_GZIP => __PACKAGE__;
push @EXPORT, qw(STORAGE_FILTER_GZIP);
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oParent,
$bWantGzip,
$strCompressType,
$iLevel,
$lCompressBufferMax,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oParent', trace => true},
{name => 'bWantGzip', optional => true, default => true, trace => true},
{name => 'strCompressType', optional => true, default => STORAGE_COMPRESS, trace => true},
{name => 'iLevel', optional => true, default => 6, trace => true},
{name => 'lCompressBufferMax', optional => true, default => COMMON_IO_BUFFER_MAX, trace => true},
);
# Bless with new class
my $self = $class->SUPER::new($oParent);
bless $self, $class;
# Set variables
$self->{bWantGzip} = $bWantGzip;
$self->{iLevel} = $iLevel;
$self->{lCompressBufferMax} = $lCompressBufferMax;
$self->{strCompressType} = $strCompressType;
# Set read/write
$self->{bWrite} = false;
# Create the zlib object
my $iZLibStatus;
if ($self->{strCompressType} eq STORAGE_COMPRESS)
{
($self->{oZLib}, $iZLibStatus) = new Compress::Raw::Zlib::Deflate(
WindowBits => $self->{bWantGzip} ? WANT_GZIP : MAX_WBITS, Level => $self->{iLevel},
Bufsize => $self->{lCompressBufferMax}, AppendOutput => 1);
$self->{tCompressedBuffer} = undef;
}
else
{
($self->{oZLib}, $iZLibStatus) = new Compress::Raw::Zlib::Inflate(
WindowBits => $self->{bWantGzip} ? WANT_GZIP : MAX_WBITS, Bufsize => $self->{lCompressBufferMax},
LimitOutput => 1, AppendOutput => 1);
$self->{tUncompressedBuffer} = undef;
$self->{lUncompressedBufferSize} = 0;
}
$self->errorCheck($iZLibStatus);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# errorCheck - check status code for errors
####################################################################################################################################
sub errorCheck
{
my $self = shift;
my $iZLibStatus = shift;
if (!($iZLibStatus == Z_OK || $iZLibStatus == Z_BUF_ERROR))
{
logErrorResult(
$self->{bWrite} ? ERROR_FILE_WRITE : ERROR_FILE_READ,
'unable to ' . ($self->{strCompressType} eq STORAGE_COMPRESS ? 'deflate' : 'inflate') . " '" .
$self->parent()->name() . "'",
$self->{oZLib}->msg());
}
return Z_OK;
}
####################################################################################################################################
# read - compress/decompress data
####################################################################################################################################
sub read
{
my $self = shift;
my $rtBuffer = shift;
my $iSize = shift;
if ($self->{strCompressType} eq STORAGE_COMPRESS)
{
return 0 if $self->eof();
my $lSizeBegin = defined($$rtBuffer) ? length($$rtBuffer) : 0;
my $lUncompressedSize;
my $lCompressedSize;
do
{
my $tUncompressedBuffer;
$lUncompressedSize = $self->parent()->read(\$tUncompressedBuffer, $iSize);
if ($lUncompressedSize > 0)
{
$self->errorCheck($self->{oZLib}->deflate($tUncompressedBuffer, $$rtBuffer));
}
else
{
$self->errorCheck($self->{oZLib}->flush($$rtBuffer));
}
$lCompressedSize = length($$rtBuffer) - $lSizeBegin;
}
while ($lUncompressedSize > 0 && $lCompressedSize < $iSize);
# Return the actual size read
return $lCompressedSize;
}
else
{
# If the local buffer size is not large enough to satisfy the request and there is still data to decompress
while ($self->{lUncompressedBufferSize} < $iSize && !$self->parent()->eof())
{
if (!defined($self->{tCompressedBuffer}) || length($self->{tCompressedBuffer}) == 0)
{
$self->parent()->read(\$self->{tCompressedBuffer}, $self->{lCompressBufferMax});
}
my $iZLibStatus = $self->{oZLib}->inflate($self->{tCompressedBuffer}, $self->{tUncompressedBuffer});
$self->{lUncompressedBufferSize} = length($self->{tUncompressedBuffer});
last if $iZLibStatus == Z_STREAM_END;
$self->errorCheck($iZLibStatus);
}
# Actual size is the lesser of the local buffer size or requested size - if the local buffer is smaller than the requested
# size it means that there was nothing more to be read
my $iActualSize = $self->{lUncompressedBufferSize} < $iSize ? $self->{lUncompressedBufferSize} : $iSize;
# Append to the request buffer
$$rtBuffer .= substr($self->{tUncompressedBuffer}, 0, $iActualSize);
# Truncate local buffer
$self->{tUncompressedBuffer} = substr($self->{tUncompressedBuffer}, $iActualSize);
$self->{lUncompressedBufferSize} -= $iActualSize;
# Return the actual size read
return $iActualSize;
}
}
####################################################################################################################################
# write - compress/decompress data
####################################################################################################################################
sub write
{
my $self = shift;
my $rtBuffer = shift;
$self->{bWrite} = true;
if ($self->{strCompressType} eq STORAGE_COMPRESS)
{
# Compress the data
$self->errorCheck($self->{oZLib}->deflate($$rtBuffer, $self->{tCompressedBuffer}));
# Only write when buffer is full
if (length($self->{tCompressedBuffer}) > $self->{lCompressBufferMax})
{
$self->parent()->write(\$self->{tCompressedBuffer});
$self->{tCompressedBuffer} = undef;
}
}
else
{
my $tCompressedBuffer = $$rtBuffer;
while (length($tCompressedBuffer) > 0)
{
my $tUncompressedBuffer;
my $iZLibStatus = $self->{oZLib}->inflate($tCompressedBuffer, $tUncompressedBuffer);
$self->parent()->write(\$tUncompressedBuffer);
last if $iZLibStatus == Z_STREAM_END;
$self->errorCheck($iZLibStatus);
}
}
# Return bytes written
return length($$rtBuffer);
}
####################################################################################################################################
# close - close the file
####################################################################################################################################
sub close
{
my $self = shift;
if (defined($self->{oZLib}))
{
# Flush the write buffer
if ($self->{bWrite})
{
if ($self->{strCompressType} eq STORAGE_COMPRESS)
{
# Flush out last compressed bytes
$self->errorCheck($self->{oZLib}->flush($self->{tCompressedBuffer}));
# Write last compressed bytes
$self->parent()->write(\$self->{tCompressedBuffer});
}
}
undef($self->{oZLib});
# Close io
return $self->parent()->close();
}
return false;
}
1;

View File

@ -1,124 +0,0 @@
####################################################################################################################################
# SHA Filter
####################################################################################################################################
package pgBackRest::Storage::Filter::Sha;
use parent 'pgBackRest::Common::Io::Filter';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
####################################################################################################################################
# Package name constant
####################################################################################################################################
use constant STORAGE_FILTER_SHA => __PACKAGE__;
push @EXPORT, qw(STORAGE_FILTER_SHA);
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oParent,
$strAlgorithm,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oParent', trace => true},
{name => 'strAlgorithm', optional => true, default => 'sha1', trace => true},
);
# Bless with new class
my $self = $class->SUPER::new($oParent);
bless $self, $class;
# Set variables
$self->{strAlgorithm} = $strAlgorithm;
# Create SHA object
$self->{oSha} = new pgBackRest::LibC::Crypto::Hash($self->{strAlgorithm});
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# read - calculate sha digest
####################################################################################################################################
sub read
{
my $self = shift;
my $rtBuffer = shift;
my $iSize = shift;
# Call the io method
my $tShaBuffer;
my $iActualSize = $self->parent()->read(\$tShaBuffer, $iSize);
# Calculate sha for the returned buffer
if ($iActualSize > 0)
{
$self->{oSha}->process($tShaBuffer);
$$rtBuffer .= $tShaBuffer;
}
# Return the actual size read
return $iActualSize;
}
####################################################################################################################################
# write - calculate sha digest
####################################################################################################################################
sub write
{
my $self = shift;
my $rtBuffer = shift;
# Calculate sha for the buffer
$self->{oSha}->process($$rtBuffer);
# Call the io method
return $self->parent()->write($rtBuffer);
}
####################################################################################################################################
# close - close the file
####################################################################################################################################
sub close
{
my $self = shift;
if (defined($self->{oSha}))
{
# Set result
$self->resultSet(STORAGE_FILTER_SHA, $self->{oSha}->result());
# Delete the sha object
delete($self->{oSha});
# Close io
return $self->parent->close();
}
return false;
}
1;

View File

@ -13,16 +13,10 @@ use File::Basename qw(basename);
use pgBackRest::Common::Log;
use pgBackRest::Config::Config;
use pgBackRest::Storage::Posix::Driver;
use pgBackRest::Storage::Local;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Storage;
use pgBackRest::Version;
####################################################################################################################################
# Storage constants
####################################################################################################################################
use constant STORAGE_LOCAL => '<LOCAL>';
push @EXPORT, qw(STORAGE_LOCAL);
####################################################################################################################################
# Compression extension
####################################################################################################################################
@ -35,11 +29,6 @@ use constant COMPRESS_EXT => 'gz';
use constant STORAGE_TEMP_EXT => PROJECT_EXE . '.tmp';
push @EXPORT, qw(STORAGE_TEMP_EXT);
####################################################################################################################################
# Cache storage so it can be retrieved quickly
####################################################################################################################################
my $hStorage;
####################################################################################################################################
# storageLocal - get local storage
#
@ -49,32 +38,13 @@ my $hStorage;
sub storageLocal
{
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
) =
logDebugParam
(
__PACKAGE__ . '::storageLocal', \@_,
{name => 'strPath', default => '/', trace => true},
);
# Create storage if not defined
if (!defined($hStorage->{&STORAGE_LOCAL}{$strPath}))
{
# Create local storage
$hStorage->{&STORAGE_LOCAL}{$strPath} = new pgBackRest::Storage::Local(
$strPath, new pgBackRest::Storage::Posix::Driver(),
{strTempExtension => STORAGE_TEMP_EXT,
lBufferMax => cfgOptionValid(CFGOPT_BUFFER_SIZE, false) ? cfgOption(CFGOPT_BUFFER_SIZE, false) : undef});
}
my ($strOperation) = logDebugParam(__PACKAGE__ . '::storageLocal');
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oStorageLocal', value => $hStorage->{&STORAGE_LOCAL}{$strPath}, trace => true},
{name => 'oStorageLocal', value => new pgBackRest::Storage::Storage(STORAGE_LOCAL), trace => true},
);
}

View File

@ -1,963 +0,0 @@
####################################################################################################################################
# Posix Storage
#
# Implements storage functions for Posix-compliant file systems.
####################################################################################################################################
package pgBackRest::Storage::Posix::Driver;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(basename dirname);
use Fcntl qw(:mode);
use File::stat qw{lstat};
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Posix::FileRead;
use pgBackRest::Storage::Posix::FileWrite;
####################################################################################################################################
# Package name constant
####################################################################################################################################
use constant STORAGE_POSIX_DRIVER => __PACKAGE__;
push @EXPORT, qw(STORAGE_POSIX_DRIVER);
####################################################################################################################################
# new
####################################################################################################################################
sub new
{
my $class = shift;
# Create the class hash
my $self = {};
bless $self, $class;
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{bFileSync},
$self->{bPathSync},
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'bFileSync', optional => true, default => true},
{name => 'bPathSync', optional => true, default => true},
);
# Set default temp extension
$self->{strTempExtension} = 'tmp';
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self, trace => true}
);
}
####################################################################################################################################
# exists - check if a path or file exists
####################################################################################################################################
sub exists
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFile,
) =
logDebugParam
(
__PACKAGE__ . '->exists', \@_,
{name => 'strFile', trace => true},
);
# Does the path/file exist?
my $bExists = true;
my $oStat = lstat($strFile);
# Use stat to test if file exists
if (defined($oStat))
{
# Check that it is actually a file
$bExists = !S_ISDIR($oStat->mode) ? true : false;
}
else
{
# If the error is not entry missing, then throw error
if (!$OS_ERROR{ENOENT})
{
logErrorResult(ERROR_FILE_EXISTS, "unable to test if file '${strFile}' exists", $OS_ERROR);
}
$bExists = false;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bExists', value => $bExists, trace => true}
);
}
####################################################################################################################################
# info - get information for path/file
####################################################################################################################################
sub info
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPathFile,
$bIgnoreMissing,
) =
logDebugParam
(
__PACKAGE__ . '->info', \@_,
{name => 'strFile', trace => true},
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
);
# Stat the path/file
my $oInfo = lstat($strPathFile);
# Check for errors
if (!defined($oInfo))
{
if (!($OS_ERROR{ENOENT} && $bIgnoreMissing))
{
logErrorResult($OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to stat '${strPathFile}'", $OS_ERROR);
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oInfo', value => $oInfo, trace => true}
);
}
####################################################################################################################################
# linkCreate
####################################################################################################################################
sub linkCreate
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strSourcePathFile,
$strDestinationLink,
$bHard,
$bPathCreate,
$bIgnoreExists,
) =
logDebugParam
(
__PACKAGE__ . '->linkCreate', \@_,
{name => 'strSourcePathFile', trace => true},
{name => 'strDestinationLink', trace => true},
{name => 'bHard', optional=> true, default => false, trace => true},
{name => 'bPathCreate', optional=> true, default => true, trace => true},
{name => 'bIgnoreExists', optional => true, default => false, trace => true},
);
if (!($bHard ? link($strSourcePathFile, $strDestinationLink) : symlink($strSourcePathFile, $strDestinationLink)))
{
my $strMessage = "unable to create link '${strDestinationLink}'";
# If parent path or source is missing
if ($OS_ERROR{ENOENT})
{
# Check if source is missing
if (!$self->exists($strSourcePathFile))
{
confess &log(ERROR, "${strMessage} because source '${strSourcePathFile}' does not exist", ERROR_FILE_MISSING);
}
if (!$bPathCreate)
{
confess &log(ERROR, "${strMessage} because parent does not exist", ERROR_PATH_MISSING);
}
# Create parent path
$self->pathCreate(dirname($strDestinationLink), {bIgnoreExists => true, bCreateParent => true});
# Create link
$self->linkCreate($strSourcePathFile, $strDestinationLink, {bHard => $bHard});
}
# Else if link already exists
elsif ($OS_ERROR{EEXIST})
{
if (!$bIgnoreExists)
{
confess &log(ERROR, "${strMessage} because it already exists", ERROR_PATH_EXISTS);
}
}
else
{
logErrorResult(ERROR_PATH_CREATE, ${strMessage}, $OS_ERROR);
}
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# linkDestination - get destination of symlink
####################################################################################################################################
sub linkDestination
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strLink,
) =
logDebugParam
(
__PACKAGE__ . '->linkDestination', \@_,
{name => 'strLink', trace => true},
);
# Get link destination
my $strLinkDestination = readlink($strLink);
# Check for errors
if (!defined($strLinkDestination))
{
logErrorResult(
$OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to get destination for link ${strLink}", $OS_ERROR);
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strLinkDestination', value => $strLinkDestination, trace => true}
);
}
####################################################################################################################################
# list - list all files/paths in path
####################################################################################################################################
sub list
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
$bIgnoreMissing,
) =
logDebugParam
(
__PACKAGE__ . '->list', \@_,
{name => 'strPath', trace => true},
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
);
# Working variables
my @stryFileList;
my $hPath;
# Attempt to open the path
if (opendir($hPath, $strPath))
{
@stryFileList = grep(!/^(\.)|(\.\.)$/i, readdir($hPath));
close($hPath);
}
# Else process errors
else
{
# Ignore the error if the file is missing and missing files should be ignored
if (!($OS_ERROR{ENOENT} && $bIgnoreMissing))
{
logErrorResult($OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to read path '${strPath}'", $OS_ERROR);
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'stryFileList', value => \@stryFileList, ref => true, trace => true}
);
}
####################################################################################################################################
# manifest - build path/file/link manifest starting with base path and including all subpaths
####################################################################################################################################
sub manifest
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
$bIgnoreMissing,
$strFilter,
) =
logDebugParam
(
__PACKAGE__ . '->manifest', \@_,
{name => 'strPath', trace => true},
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
{name => 'strFilter', optional => true, trace => true},
);
# Generate the manifest
my $hManifest = {};
$self->manifestRecurse($strPath, undef, 0, $hManifest, $bIgnoreMissing, $strFilter);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'hManifest', value => $hManifest, trace => true}
);
}
sub manifestRecurse
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
$strSubPath,
$iDepth,
$hManifest,
$bIgnoreMissing,
$strFilter,
) =
logDebugParam
(
__PACKAGE__ . '::manifestRecurse', \@_,
{name => 'strPath', trace => true},
{name => 'strSubPath', required => false, trace => true},
{name => 'iDepth', default => 0, trace => true},
{name => 'hManifest', required => false, trace => true},
{name => 'bIgnoreMissing', required => false, default => false, trace => true},
{name => 'strFilter', required => false, trace => true},
);
# Set operation and debug strings
my $strPathRead = $strPath . (defined($strSubPath) ? "/${strSubPath}" : '');
my $hPath;
# If this is the top level stat the path to discover if it is actually a file
my $oPathInfo = $self->info($strPathRead, {bIgnoreMissing => $bIgnoreMissing});
if (defined($oPathInfo))
{
# If the initial path passed is a file then generate the manifest for just that file
if ($iDepth == 0 && !S_ISDIR($oPathInfo->mode()))
{
$hManifest->{basename($strPathRead)} = $self->manifestStat($strPathRead);
}
# Else read as a normal directory
else
{
# Get a list of all files in the path (including .)
my @stryFileList = @{$self->list($strPathRead, {bIgnoreMissing => $iDepth != 0})};
unshift(@stryFileList, '.');
my $hFileStat = $self->manifestList($strPathRead, \@stryFileList, $strFilter);
# Loop through all subpaths/files in the path
foreach my $strFile (keys(%{$hFileStat}))
{
my $strManifestFile = $iDepth == 0 ? $strFile : ($strSubPath . ($strFile eq qw(.) ? '' : "/${strFile}"));
$hManifest->{$strManifestFile} = $hFileStat->{$strFile};
# Recurse into directories
if ($hManifest->{$strManifestFile}{type} eq 'd' && $strFile ne qw(.))
{
$self->manifestRecurse($strPath, $strManifestFile, $iDepth + 1, $hManifest);
}
}
}
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
sub manifestList
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
$stryFile,
$strFilter,
) =
logDebugParam
(
__PACKAGE__ . '->manifestList', \@_,
{name => 'strPath', trace => true},
{name => 'stryFile', trace => true},
{name => 'strFilter', required => false, trace => true},
);
my $hFileStat = {};
foreach my $strFile (@{$stryFile})
{
if ($strFile ne '.' && defined($strFilter) && $strFilter ne $strFile)
{
next;
}
$hFileStat->{$strFile} = $self->manifestStat("${strPath}" . ($strFile eq qw(.) ? '' : "/${strFile}"));
if (!defined($hFileStat->{$strFile}))
{
delete($hFileStat->{$strFile});
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'hFileStat', value => $hFileStat, trace => true}
);
}
sub manifestStat
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFile,
) =
logDebugParam
(
__PACKAGE__ . '->manifestStat', \@_,
{name => 'strFile', trace => true},
);
# Stat the path/file, ignoring any that are missing
my $oStat = $self->info($strFile, {bIgnoreMissing => true});
# Generate file data if stat succeeded (i.e. file exists)
my $hFile;
if (defined($oStat))
{
# Check for regular file
if (S_ISREG($oStat->mode))
{
$hFile->{type} = 'f';
# Get size
$hFile->{size} = $oStat->size;
# Get modification time
$hFile->{modification_time} = $oStat->mtime;
}
# Check for directory
elsif (S_ISDIR($oStat->mode))
{
$hFile->{type} = 'd';
}
# Check for link
elsif (S_ISLNK($oStat->mode))
{
$hFile->{type} = 'l';
$hFile->{link_destination} = $self->linkDestination($strFile);
}
# Not a recognized type
else
{
confess &log(ERROR, "${strFile} is not of type directory, file, or link", ERROR_FILE_INVALID);
}
# Get user name
$hFile->{user} = getpwuid($oStat->uid);
# Get group name
$hFile->{group} = getgrgid($oStat->gid);
# Get mode
if ($hFile->{type} ne 'l')
{
$hFile->{mode} = sprintf('%04o', S_IMODE($oStat->mode));
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'hFile', value => $hFile, trace => true}
);
}
####################################################################################################################################
# move - move path/file
####################################################################################################################################
sub move
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strSourceFile,
$strDestinationFile,
$bPathCreate,
) =
logDebugParam
(
__PACKAGE__ . '->move', \@_,
{name => 'strSourceFile', trace => true},
{name => 'strDestinationFile', trace => true},
{name => 'bPathCreate', default => false, trace => true},
);
# Get source and destination paths
my $strSourcePathFile = dirname($strSourceFile);
my $strDestinationPathFile = dirname($strDestinationFile);
# Move the file
if (!rename($strSourceFile, $strDestinationFile))
{
my $strMessage = "unable to move '${strSourceFile}'";
# If something is missing determine if it is the source or destination
if ($OS_ERROR{ENOENT})
{
if (!$self->exists($strSourceFile))
{
logErrorResult(ERROR_FILE_MISSING, "${strMessage} because it is missing");
}
if ($bPathCreate)
{
# Attempt to create the path - ignore exists here in case another process creates it first
$self->pathCreate($strDestinationPathFile, {bCreateParent => true, bIgnoreExists => true});
# Try move again
$self->move($strSourceFile, $strDestinationFile);
}
else
{
logErrorResult(ERROR_PATH_MISSING, "${strMessage} to missing path '${strDestinationPathFile}'");
}
}
# Else raise the error
else
{
logErrorResult(ERROR_FILE_MOVE, "${strMessage} to '${strDestinationFile}'", $OS_ERROR);
}
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# openRead - open file for reading
####################################################################################################################################
sub openRead
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFile,
$bIgnoreMissing,
) =
logDebugParam
(
__PACKAGE__ . '->openRead', \@_,
{name => 'strFile', trace => true},
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
);
my $oFileIO = new pgBackRest::Storage::Posix::FileRead($self, $strFile, {bIgnoreMissing => $bIgnoreMissing});
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oFileIO', value => $oFileIO, trace => true},
);
}
####################################################################################################################################
# openWrite - open file for writing
####################################################################################################################################
sub openWrite
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFile,
$strMode,
$strUser,
$strGroup,
$lTimestamp,
$bPathCreate,
$bAtomic,
) =
logDebugParam
(
__PACKAGE__ . '->openWrite', \@_,
{name => 'strFile', trace => true},
{name => 'strMode', optional => true, trace => true},
{name => 'strUser', optional => true, trace => true},
{name => 'strGroup', optional => true, trace => true},
{name => 'lTimestamp', optional => true, trace => true},
{name => 'bPathCreate', optional => true, trace => true},
{name => 'bAtomic', optional => true, trace => true},
);
my $oFileIO = new pgBackRest::Storage::Posix::FileWrite(
$self, $strFile,
{strMode => $strMode, strUser => $strUser, strGroup => $strGroup, lTimestamp => $lTimestamp, bPathCreate => $bPathCreate,
bAtomic => $bAtomic, bSync => $self->{bFileSync}});
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oFileIO', value => $oFileIO, trace => true},
);
}
####################################################################################################################################
# owner - change ownership of path/file
####################################################################################################################################
sub owner
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFilePath,
$strUser,
$strGroup,
) =
logDebugParam
(
__PACKAGE__ . '->owner', \@_,
{name => 'strFilePath', trace => true},
{name => 'strUser', optional => true, trace => true},
{name => 'strGroup', optional => true, trace => true},
);
# Only proceed if user or group was specified
if (defined($strUser) || defined($strGroup))
{
my $strMessage = "unable to set ownership for '${strFilePath}'";
my $iUserId;
my $iGroupId;
# If the user or group is not defined then get it by stat'ing the file. This is because the chown function requires that
# both user and group be set.
my $oStat = $self->info($strFilePath);
if (!defined($strUser))
{
$iUserId = $oStat->uid;
}
if (!defined($strGroup))
{
$iGroupId = $oStat->gid;
}
# Lookup user if specified
if (defined($strUser))
{
$iUserId = getpwnam($strUser);
if (!defined($iUserId))
{
logErrorResult(ERROR_FILE_OWNER, "${strMessage} because user '${strUser}' does not exist");
}
}
# Lookup group if specified
if (defined($strGroup))
{
$iGroupId = getgrnam($strGroup);
if (!defined($iGroupId))
{
logErrorResult(ERROR_FILE_OWNER, "${strMessage} because group '${strGroup}' does not exist");
}
}
# Set ownership on the file if the user or group would be changed
if ($iUserId != $oStat->uid || $iGroupId != $oStat->gid)
{
if (!chown($iUserId, $iGroupId, $strFilePath))
{
logErrorResult(ERROR_FILE_OWNER, "${strMessage}", $OS_ERROR);
}
}
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# pathCreate - create path
####################################################################################################################################
sub pathCreate
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
$strMode,
$bIgnoreExists,
$bCreateParent,
) =
logDebugParam
(
__PACKAGE__ . '->pathCreate', \@_,
{name => 'strPath', trace => true},
{name => 'strMode', optional => true, default => '0750', trace => true},
{name => 'bIgnoreExists', optional => true, default => false, trace => true},
{name => 'bCreateParent', optional => true, default => false, trace => true},
);
# Attempt to create the directory
if (!mkdir($strPath, oct($strMode)))
{
my $strMessage = "unable to create path '${strPath}'";
# If parent path is missing
if ($OS_ERROR{ENOENT})
{
if (!$bCreateParent)
{
confess &log(ERROR, "${strMessage} because parent does not exist", ERROR_PATH_MISSING);
}
# Create parent path
$self->pathCreate(dirname($strPath), {strMode => $strMode, bIgnoreExists => true, bCreateParent => $bCreateParent});
# Create path
$self->pathCreate($strPath, {strMode => $strMode, bIgnoreExists => true});
}
# Else if path already exists
elsif ($OS_ERROR{EEXIST})
{
if (!$bIgnoreExists)
{
confess &log(ERROR, "${strMessage} because it already exists", ERROR_PATH_EXISTS);
}
}
else
{
logErrorResult(ERROR_PATH_CREATE, ${strMessage}, $OS_ERROR);
}
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# pathExists - check if path exists
####################################################################################################################################
sub pathExists
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
) =
logDebugParam
(
__PACKAGE__ . '->pathExists', \@_,
{name => 'strPath', trace => true},
);
# Does the path/file exist?
my $bExists = true;
my $oStat = lstat($strPath);
# Use stat to test if path exists
if (defined($oStat))
{
# Check that it is actually a path
$bExists = S_ISDIR($oStat->mode) ? true : false;
}
else
{
# If the error is not entry missing, then throw error
if (!$OS_ERROR{ENOENT})
{
logErrorResult(ERROR_FILE_EXISTS, "unable to test if path '${strPath}' exists", $OS_ERROR);
}
$bExists = false;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bExists', value => $bExists, trace => true}
);
}
####################################################################################################################################
# pathSync - perform fsync on path
####################################################################################################################################
sub pathSync
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
) =
logDebugParam
(
__PACKAGE__ . '->pathSync', \@_,
{name => 'strPath', trace => true},
);
open(my $hPath, "<", $strPath)
or confess &log(ERROR, "unable to open '${strPath}' for sync", ERROR_PATH_OPEN);
open(my $hPathDup, ">&", $hPath)
or confess &log(ERROR, "unable to duplicate '${strPath}' handle for sync", ERROR_PATH_OPEN);
$hPathDup->sync()
or confess &log(ERROR, "unable to sync path '${strPath}'", ERROR_PATH_SYNC);
close($hPathDup);
close($hPath);
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# remove - remove path/file
####################################################################################################################################
sub remove
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPathFile,
$bIgnoreMissing,
$bRecurse,
) =
logDebugParam
(
__PACKAGE__ . '->remove', \@_,
{name => 'strPathFile', trace => true},
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
{name => 'bRecurse', optional => true, default => false, trace => true},
);
# Working variables
my $bRemoved = true;
# Remove a tree
if ($bRecurse)
{
# Dynamically load the driver
require pgBackRest::LibC;
pgBackRest::LibC->import(qw(:storage));
storagePosixPathRemove($strPathFile, !$bIgnoreMissing, $bRecurse)
}
# Only remove the specified file
else
{
foreach my $strFile (ref($strPathFile) ? @{$strPathFile} : ($strPathFile))
{
if (unlink($strFile) != 1)
{
$bRemoved = false;
# Throw error if this is not an ignored missing file
if (!($OS_ERROR{ENOENT} && $bIgnoreMissing))
{
logErrorResult(
$OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to remove file '${strFile}'", $OS_ERROR);
}
}
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bRemoved', value => $bRemoved, trace => true}
);
}
####################################################################################################################################
# Getters/Setters
####################################################################################################################################
sub capability {true}
sub className {STORAGE_POSIX_DRIVER}
sub tempExtension {shift->{strTempExtension}}
sub tempExtensionSet {my $self = shift; $self->{strTempExtension} = shift}
1;

View File

@ -1,105 +0,0 @@
####################################################################################################################################
# Posix File Read
####################################################################################################################################
package pgBackRest::Storage::Posix::FileRead;
use parent 'pgBackRest::Common::Io::Handle';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Fcntl qw(O_RDONLY);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oDriver,
$strName,
$bIgnoreMissing,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oDriver', trace => true},
{name => 'strName', trace => true},
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
);
# Open the file
my $fhFile;
if (!sysopen($fhFile, $strName, O_RDONLY))
{
if (!($OS_ERROR{ENOENT} && $bIgnoreMissing))
{
logErrorResult($OS_ERROR{ENOENT} ? ERROR_FILE_MISSING : ERROR_FILE_OPEN, "unable to open '${strName}'", $OS_ERROR);
}
undef($fhFile);
}
# Create IO object if open succeeded
my $self;
if (defined($fhFile))
{
# Set file mode to binary
binmode($fhFile);
# Create the class hash
$self = $class->SUPER::new("'${strName}'", $fhFile);
bless $self, $class;
# Set variables
$self->{oDriver} = $oDriver;
$self->{strName} = $strName;
$self->{fhFile} = $fhFile;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self, trace => true}
);
}
####################################################################################################################################
# close - close the file
####################################################################################################################################
sub close
{
my $self = shift;
if (defined($self->handle()))
{
# Close the file
close($self->handle());
undef($self->{fhFile});
# Close parent
$self->SUPER::close();
}
return true;
}
####################################################################################################################################
# Getters
####################################################################################################################################
sub handle {shift->{fhFile}}
sub name {shift->{strName}}
1;

View File

@ -1,209 +0,0 @@
####################################################################################################################################
# Posix File Write
####################################################################################################################################
package pgBackRest::Storage::Posix::FileWrite;
use parent 'pgBackRest::Common::Io::Handle';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Fcntl qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC);
use File::Basename qw(dirname);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Common::Io::Handle;
use pgBackRest::Storage::Base;
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oDriver,
$strName,
$strMode,
$strUser,
$strGroup,
$lTimestamp,
$bPathCreate,
$bAtomic,
$bSync,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oDriver', trace => true},
{name => 'strName', trace => true},
{name => 'strMode', optional => true, trace => true},
{name => 'strUser', optional => true, trace => true},
{name => 'strGroup', optional => true, trace => true},
{name => 'lTimestamp', optional => true, trace => true},
{name => 'bPathCreate', optional => true, default => false, trace => true},
{name => 'bAtomic', optional => true, default => false, trace => true},
{name => 'bSync', optional => true, default => true, trace => true},
);
# Create the class hash
my $self = $class->SUPER::new("'${strName}'");
bless $self, $class;
# Set variables
$self->{oDriver} = $oDriver;
$self->{strName} = $strName;
$self->{strMode} = $strMode;
$self->{strUser} = $strUser;
$self->{strGroup} = $strGroup;
$self->{lTimestamp} = $lTimestamp;
$self->{bPathCreate} = $bPathCreate;
$self->{bAtomic} = $bAtomic;
$self->{bSync} = $bSync;
# If atomic create temp filename
if ($self->{bAtomic})
{
# Create temp file name
$self->{strNameTmp} = "$self->{strName}." . $self->{oDriver}->tempExtension();
}
# Open file on first write to avoid creating extraneous files on error
$self->{bOpened} = false;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self, trace => true}
);
}
####################################################################################################################################
# open - open the file
####################################################################################################################################
sub open
{
my $self = shift;
# Get the file name
my $strFile = $self->{bAtomic} ? $self->{strNameTmp} : $self->{strName};
# Open the file
if (!sysopen(
$self->{fhFile}, $strFile, O_WRONLY | O_CREAT | O_TRUNC, oct(defined($self->{strMode}) ? $self->{strMode} : '0666')))
{
# If the path does not exist create it if requested
if ($OS_ERROR{ENOENT} && $self->{bPathCreate})
{
$self->{oDriver}->pathCreate(dirname($strFile), {bIgnoreExists => true, bCreateParent => true});
$self->{bPathCreate} = false;
return $self->open();
}
logErrorResult($OS_ERROR{ENOENT} ? ERROR_PATH_MISSING : ERROR_FILE_OPEN, "unable to open '${strFile}'", $OS_ERROR);
}
# Set file mode to binary
binmode($self->{fhFile});
# Set the owner
$self->{oDriver}->owner($strFile, {strUser => $self->{strUser}, strGroup => $self->{strGroup}});
# Set handle
$self->handleWriteSet($self->{fhFile});
# Mark file as opened
$self->{bOpened} = true;
return true;
}
####################################################################################################################################
# write - write data to a file
####################################################################################################################################
sub write
{
my $self = shift;
my $rtBuffer = shift;
# Open file if it is not open already
$self->open() if !$self->opened();
return $self->SUPER::write($rtBuffer);
}
####################################################################################################################################
# close - close the file
####################################################################################################################################
sub close
{
my $self = shift;
if (defined($self->handle()))
{
# Sync the file
if ($self->{bSync})
{
$self->handle()->sync();
}
# Close the file
close($self->handle());
undef($self->{fhFile});
# Get current filename
my $strCurrentName = $self->{bAtomic} ? $self->{strNameTmp} : $self->{strName};
# Set the modification time
if (defined($self->{lTimestamp}))
{
utime(time(), $self->{lTimestamp}, $strCurrentName)
or logErrorResult(ERROR_FILE_WRITE, "unable to set time for '${strCurrentName}'", $OS_ERROR);
}
# Move the file from temp to final if atomic
if ($self->{bAtomic})
{
$self->{oDriver}->move($strCurrentName, $self->{strName});
}
# Set result
$self->resultSet(COMMON_IO_HANDLE, $self->{lSize});
# Close parent
$self->SUPER::close();
}
return true;
}
####################################################################################################################################
# Close the handle if it is open (in case close() was never called)
####################################################################################################################################
sub DESTROY
{
my $self = shift;
if (defined($self->handle()))
{
CORE::close($self->handle());
undef($self->{fhFile});
}
}
####################################################################################################################################
# Getters
####################################################################################################################################
sub handle {shift->{fhFile}}
sub opened {shift->{bOpened}}
sub name {shift->{strName}}
1;

View File

@ -1,283 +0,0 @@
####################################################################################################################################
# S3 Authentication
#
# Contains the functions required to do S3 authentication. It's a complicated topic and too much to cover here, but there is
# excellent documentation at http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html.
####################################################################################################################################
package pgBackRest::Storage::S3::Auth;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Digest::SHA qw(hmac_sha256 hmac_sha256_hex);
use Exporter qw(import);
our @EXPORT = qw();
use POSIX qw(strftime);
use pgBackRest::Common::Http::Common;
use pgBackRest::Common::Log;
use pgBackRest::LibC qw(:crypto);
####################################################################################################################################
# Constants
####################################################################################################################################
use constant S3 => 's3';
use constant AWS4 => 'AWS4';
use constant AWS4_REQUEST => 'aws4_request';
use constant AWS4_HMAC_SHA256 => 'AWS4-HMAC-SHA256';
use constant S3_HEADER_AUTHORIZATION => 'authorization';
push @EXPORT, qw(S3_HEADER_AUTHORIZATION);
use constant S3_HEADER_DATE => 'x-amz-date';
push @EXPORT, qw(S3_HEADER_DATE);
use constant S3_HEADER_CONTENT_SHA256 => 'x-amz-content-sha256';
push @EXPORT, qw(S3_HEADER_CONTENT_SHA256);
use constant S3_HEADER_HOST => 'host';
push @EXPORT, qw(S3_HEADER_HOST);
use constant S3_HEADER_TOKEN => 'x-amz-security-token';
push @EXPORT, qw(S3_HEADER_TOKEN);
use constant PAYLOAD_DEFAULT_HASH => cryptoHashOne('sha256', '');
push @EXPORT, qw(PAYLOAD_DEFAULT_HASH);
####################################################################################################################################
# s3DateTime - format date/time for authentication
####################################################################################################################################
sub s3DateTime
{
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$lTime,
) =
logDebugParam
(
__PACKAGE__ . '::s3DateTime', \@_,
{name => 'lTime', default => time(), trace => true},
);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strDateTime', value => strftime("%Y%m%dT%H%M%SZ", gmtime($lTime)), trace => true}
);
}
push @EXPORT, qw(s3DateTime);
####################################################################################################################################
# s3CanonicalRequest - strictly formatted version of the HTTP request used for signing
####################################################################################################################################
sub s3CanonicalRequest
{
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strVerb,
$strUri,
$strQuery,
$hHeader,
$strPayloadHash,
) =
logDebugParam
(
__PACKAGE__ . '::s3CanonicalRequest', \@_,
{name => 'strVerb', trace => true},
{name => 'strUri', trace => true},
{name => 'strQuery', trace => true},
{name => 'hHeader', trace => true},
{name => 'strPayloadHash', trace => true},
);
# Create the canonical request
my $strCanonicalRequest =
"${strVerb}\n${strUri}\n${strQuery}\n";
my $strSignedHeaders;
foreach my $strHeader (sort(keys(%{$hHeader})))
{
if (lc($strHeader) ne $strHeader)
{
confess &log(ASSERT, "header '${strHeader}' must be lower case");
}
$strCanonicalRequest .= $strHeader . ":$hHeader->{$strHeader}\n";
$strSignedHeaders .= (defined($strSignedHeaders) ? qw(;) : '') . lc($strHeader);
}
$strCanonicalRequest .= "\n${strSignedHeaders}\n${strPayloadHash}";
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strCanonicalRequest', value => $strCanonicalRequest, trace => true},
{name => 'strSignedHeaders', value => $strSignedHeaders, trace => true},
);
}
push @EXPORT, qw(s3CanonicalRequest);
####################################################################################################################################
# s3SigningKey - signing keys last for seven days, but we'll regenerate every day because it doesn't seem too burdensome
####################################################################################################################################
my $hSigningKeyCache; # Cache signing keys rather than regenerating them every time
sub s3SigningKey
{
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strDate,
$strRegion,
$strSecretAccessKey,
) =
logDebugParam
(
__PACKAGE__ . '::s3SigningKey', \@_,
{name => 'strDate', trace => true},
{name => 'strRegion', trace => true},
{name => 'strSecretAccessKey', redact => true, trace => true},
);
# Check for signing key in cache
my $strSigningKey = $hSigningKeyCache->{$strDate}{$strRegion}{$strSecretAccessKey};
# If not found then generate it
if (!defined($strSigningKey))
{
my $strDateKey = hmac_sha256($strDate, AWS4 . $strSecretAccessKey);
my $strRegionKey = hmac_sha256($strRegion, $strDateKey);
my $strServiceKey = hmac_sha256(S3, $strRegionKey);
$strSigningKey = hmac_sha256(AWS4_REQUEST, $strServiceKey);
# Cache the signing key
$hSigningKeyCache->{$strDate}{$strRegion}{$strSecretAccessKey} = $strSigningKey;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strSigningKey', value => $strSigningKey, redact => true, trace => true}
);
}
push @EXPORT, qw(s3SigningKey);
####################################################################################################################################
# s3StringToSign - string that will be signed by the signing key for authentication
####################################################################################################################################
sub s3StringToSign
{
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strDateTime,
$strRegion,
$strCanonicalRequestHash,
) =
logDebugParam
(
__PACKAGE__ . '::s3StringToSign', \@_,
{name => 'strDateTime', trace => true},
{name => 'strRegion', trace => true},
{name => 'strCanonicalRequestHash', trace => true},
);
my $strStringToSign =
AWS4_HMAC_SHA256 . "\n${strDateTime}\n" . substr($strDateTime, 0, 8) . "/${strRegion}/" . S3 . '/' . AWS4_REQUEST . "\n" .
$strCanonicalRequestHash;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strStringToSign', value => $strStringToSign, trace => true}
);
}
push @EXPORT, qw(s3StringToSign);
####################################################################################################################################
# s3AuthorizationHeader - authorization string that will be used in the HTTP "authorization" header
####################################################################################################################################
sub s3AuthorizationHeader
{
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strRegion,
$strHost,
$strVerb,
$strUri,
$strQuery,
$strDateTime,
$hHeader,
$strAccessKeyId,
$strSecretAccessKey,
$strSecurityToken,
$strPayloadHash,
) =
logDebugParam
(
__PACKAGE__ . '::s3AuthorizationHeader', \@_,
{name => 'strRegion', trace => true},
{name => 'strHost', trace => true},
{name => 'strVerb', trace => true},
{name => 'strUri', trace => true},
{name => 'strQuery', trace => true},
{name => 'strDateTime', trace => true},
{name => 'hHeader', required => false, trace => true},
{name => 'strAccessKeyId', redact => true, trace => true},
{name => 'strSecretAccessKey', redact => true, trace => true},
{name => 'strSecurityToken', required => false, redact => true, trace => true},
{name => 'strPayloadHash', trace => true},
);
# Delete the authorization header if it already exists. This could happen on a retry.
delete($hHeader->{&S3_HEADER_AUTHORIZATION});
# Add s3 required headers
$hHeader->{&S3_HEADER_HOST} = $strHost;
$hHeader->{&S3_HEADER_CONTENT_SHA256} = $strPayloadHash;
$hHeader->{&S3_HEADER_DATE} = $strDateTime;
# Add security token if defined
if (defined($strSecurityToken))
{
$hHeader->{&S3_HEADER_TOKEN} = $strSecurityToken;
}
# Create authorization string
my ($strCanonicalRequest, $strSignedHeaders) = s3CanonicalRequest(
$strVerb, httpUriEncode($strUri, true), $strQuery, $hHeader, $strPayloadHash);
my $strStringToSign = s3StringToSign($strDateTime, $strRegion, cryptoHashOne('sha256', $strCanonicalRequest));
$hHeader->{&S3_HEADER_AUTHORIZATION} =
AWS4_HMAC_SHA256 . " Credential=${strAccessKeyId}/" . substr($strDateTime, 0, 8) . "/${strRegion}/" . S3 . qw(/) .
AWS4_REQUEST . ",SignedHeaders=${strSignedHeaders},Signature=" . hmac_sha256_hex($strStringToSign,
s3SigningKey(substr($strDateTime, 0, 8), $strRegion, $strSecretAccessKey));
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'hHeader', value => $hHeader, trace => true},
{name => 'strCanonicalRequest', value => $strCanonicalRequest, trace => true},
{name => 'strSignedHeaders', value => $strSignedHeaders, trace => true},
{name => 'strStringToSign', value => $strStringToSign, trace => true},
);
}
push @EXPORT, qw(s3AuthorizationHeader);
1;

View File

@ -1,507 +0,0 @@
####################################################################################################################################
# S3 Storage
####################################################################################################################################
package pgBackRest::Storage::S3::Driver;
use parent 'pgBackRest::Storage::S3::Request';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use Digest::MD5 qw(md5_base64);
use File::Basename qw(basename dirname);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Common::Xml;
use pgBackRest::Storage::S3::FileRead;
use pgBackRest::Storage::S3::FileWrite;
use pgBackRest::Storage::S3::Request;
use pgBackRest::Storage::S3::Info;
####################################################################################################################################
# Package name constant
####################################################################################################################################
use constant STORAGE_S3_DRIVER => __PACKAGE__;
push @EXPORT, qw(STORAGE_S3_DRIVER);
####################################################################################################################################
# Query constants
####################################################################################################################################
use constant S3_QUERY_CONTINUATION_TOKEN => 'continuation-token';
use constant S3_QUERY_DELIMITER => 'delimiter';
use constant S3_QUERY_LIST_TYPE => 'list-type';
use constant S3_QUERY_PREFIX => 'prefix';
####################################################################################################################################
# Batch maximum size
####################################################################################################################################
use constant S3_BATCH_MAX => 1000;
####################################################################################################################################
# openWrite - open a file for write
####################################################################################################################################
sub openWrite
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFile,
) =
logDebugParam
(
__PACKAGE__ . '->openWrite', \@_,
{name => 'strFile', trace => true},
);
my $oFileIO = new pgBackRest::Storage::S3::FileWrite($self, $strFile);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oFileIO', value => $oFileIO, trace => true},
);
}
####################################################################################################################################
# openRead
####################################################################################################################################
sub openRead
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFile,
$bIgnoreMissing,
) =
logDebugParam
(
__PACKAGE__ . '->openRead', \@_,
{name => 'strFile', trace => true},
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
);
my $oFileIO = new pgBackRest::Storage::S3::FileRead($self, $strFile, {bIgnoreMissing => $bIgnoreMissing});
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oFileIO', value => $oFileIO, trace => true},
);
}
####################################################################################################################################
# manifest
####################################################################################################################################
sub manifest
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
$bRecurse,
$bPath,
) =
logDebugParam
(
__PACKAGE__ . '->manifest', \@_,
{name => 'strPath', trace => true},
# Optional parameters not part of the driver spec
{name => 'bRecurse', optional => true, default => true, trace => true},
{name => 'bPath', optional => true, default => true, trace => true},
);
# Determine the prefix (this is the search path within the bucket)
my $strPrefix = $strPath eq qw{/} ? undef : substr($strPath, 1) . ($bPath ? qw{/} : '');
# A delimiter must be used if recursion is not desired
my $strDelimiter = $bRecurse ? undef : '/';
# Hash to hold the manifest
my $hManifest = {};
# Continuation token - returned from requests where there is more data to be fetched
my $strContinuationToken;
do
{
# Get the file list
my $oResponse = $self->request(
HTTP_VERB_GET, {hQuery =>
{&S3_QUERY_LIST_TYPE => 2, &S3_QUERY_PREFIX => $strPrefix, &S3_QUERY_DELIMITER => $strDelimiter,
&S3_QUERY_CONTINUATION_TOKEN => $strContinuationToken}, strResponseType => S3_RESPONSE_TYPE_XML});
# Modify the prefix for file searches so the filename is not stripped off
if (defined($strPrefix) && !$bPath)
{
# If there are no paths in the prefix then undef it
if (index($strPrefix, qw{/}) == -1)
{
undef($strPrefix);
}
else
{
$strPrefix = dirname($strPrefix) . qw{/};
}
}
# Store files
foreach my $oFile (xmlTagChildren($oResponse, "Contents"))
{
my $strName = xmlTagText($oFile, "Key");
# Strip off prefix
if (defined($strPrefix))
{
$strName = substr($strName, length($strPrefix));
}
$hManifest->{$strName}->{type} = 'f';
$hManifest->{$strName}->{size} = xmlTagText($oFile, 'Size') + 0;
# Generate paths from the name if recursing
if ($bRecurse)
{
my @stryName = split(qw{/}, $strName);
if (@stryName > 1)
{
$strName = undef;
for (my $iIndex = 0; $iIndex < @stryName - 1; $iIndex++)
{
$strName .= (defined($strName) ? qw{/} : '') . $stryName[$iIndex];
$hManifest->{$strName}->{type} = 'd';
}
}
}
}
# Store directories
if ($bPath && !$bRecurse)
{
foreach my $oPath (xmlTagChildren($oResponse, "CommonPrefixes"))
{
my $strName = xmlTagText($oPath, "Prefix");
# Strip off prefix
if (defined($strPrefix))
{
$strName = substr($strName, length($strPrefix));
}
# Strip off final /
$strName = substr($strName, 0, length($strName) - 1);
$hManifest->{$strName}->{type} = 'd';
}
}
$strContinuationToken = xmlTagText($oResponse, "NextContinuationToken", false);
}
while (defined($strContinuationToken));
# Add . for the initial path (this is just for compatibility with filesystems that have directories)
if ($bPath)
{
$hManifest->{qw{.}}->{type} = 'd';
}
# use Data::Dumper; &log(WARN, 'MANIFEST' . Dumper($hManifest));
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'hManifest', value => $hManifest, trace => true}
);
}
####################################################################################################################################
# list - list a directory
####################################################################################################################################
sub list
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
$strExpression
) =
logDebugParam
(
__PACKAGE__ . '->list', \@_,
{name => 'strPath', trace => true},
{name => 'strExpression', optional => true, trace => true},
);
# Use the regexp to build a prefix to shorten searches
my $strPrefix = regexPrefix($strExpression);
# Get list using manifest function
my @stryFileList = grep(
!/^\.$/i, keys(%{$self->manifest(
$strPath . (defined($strPrefix) ? "/${strPrefix}" : ''), {bRecurse => false, bPath => !defined($strPrefix)})}));
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'stryFileList', value => \@stryFileList, ref => true, trace => true}
);
}
####################################################################################################################################
# pathCreate - directories do no exist in s3 so this is a noop
####################################################################################################################################
sub pathCreate
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
) =
logDebugParam
(
__PACKAGE__ . '->pathCreate', \@_,
{name => 'strPath', trace => true},
);
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# pathSync - directories do not exist in s3 so this is a noop
####################################################################################################################################
sub pathSync
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
) =
logDebugParam
(
__PACKAGE__ . '->pathSync', \@_,
{name => 'strPath', trace => true},
);
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# exists - check if a file exists
####################################################################################################################################
sub exists
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFile,
) =
logDebugParam
(
__PACKAGE__ . '->exists', \@_,
{name => 'strFile', trace => true},
);
# Does the path/file exist?
my $bExists = defined($self->manifest($strFile, {bRecurse => false, bPath => false})->{basename($strFile)}) ? true : false;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bExists', value => $bExists, trace => true}
);
}
####################################################################################################################################
# pathExists
####################################################################################################################################
sub pathExists
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strPath,
) =
logDebugParam
(
__PACKAGE__ . '->pathExists', \@_,
{name => 'strPath', trace => true},
);
my $bExists = true;
# Only check if path <> /
if ($strPath ne qw{/})
{
# Does the path exist?
my $rhInfo = $self->manifest(dirname($strPath), {bRecurse => false, bPath => true})->{basename($strPath)};
$bExists = defined($rhInfo) && $rhInfo->{type} eq 'd' ? true : false;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bExists', value => $bExists, trace => true}
);
}
####################################################################################################################################
# info - get information about a file
####################################################################################################################################
sub info
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFile,
) =
logDebugParam
(
__PACKAGE__ . '->info', \@_,
{name => 'strFile', trace => true},
);
# Get the file
my $rhFile = $self->manifest($strFile, {bRecurse => false, bPath => false})->{basename($strFile)};
if (!defined($rhFile))
{
confess &log(ERROR, "unable to get info for missing file ${strFile}", ERROR_FILE_MISSING);
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oInfo', value => new pgBackRest::Storage::S3::Info($rhFile->{size}), trace => true}
);
}
####################################################################################################################################
# remove
####################################################################################################################################
sub remove
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$rstryFile,
$bRecurse,
) =
logDebugParam
(
__PACKAGE__ . '->remove', \@_,
{name => 'rstryFile', trace => true},
{name => 'bRecurse', optional => true, default => false, trace => true},
);
# Remove a tree
if ($bRecurse)
{
my $rhManifest = $self->manifest($rstryFile);
my @stryRemoveFile;
# Iterate all files in the manifest
foreach my $strFile (sort({$b cmp $a} keys(%{$rhManifest})))
{
next if $rhManifest->{$strFile}->{type} eq 'd';
push(@stryRemoveFile, "${rstryFile}/${strFile}");
}
# Remove files
if (@stryRemoveFile > 0)
{
$self->remove(\@stryRemoveFile);
}
}
# Only remove the specified file
else
{
# If stryFile is a scalar, convert to an array
my $rstryFileAll = ref($rstryFile) ? $rstryFile : [$rstryFile];
do
{
my $strFile = shift(@{$rstryFileAll});
my $iTotal = 0;
my $strXml = XML_HEADER . '<Delete><Quiet>true</Quiet>';
while (defined($strFile))
{
$iTotal++;
$strXml .= '<Object><Key>' . xmlFromText(substr($strFile, 1)) . '</Key></Object>';
$strFile = $iTotal < S3_BATCH_MAX ? shift(@{$rstryFileAll}) : undef;
}
$strXml .= '</Delete>';
my $hHeader = {'content-md5' => md5_base64($strXml) . '=='};
# Delete a file
my $oResponse = $self->request(
HTTP_VERB_POST,
{hQuery => 'delete=', rstrBody => \$strXml, hHeader => $hHeader, strResponseType => S3_RESPONSE_TYPE_XML});
}
while (@{$rstryFileAll} > 0);
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bResult', value => true, trace => true}
);
}
####################################################################################################################################
# Getters
####################################################################################################################################
sub capability {false}
sub className {STORAGE_S3_DRIVER}
1;

View File

@ -1,68 +0,0 @@
####################################################################################################################################
# S3 File Read
####################################################################################################################################
package pgBackRest::Storage::S3::FileRead;
use parent 'pgBackRest::Common::Http::Client';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Digest::MD5 qw(md5_base64);
use Fcntl qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC);
use File::Basename qw(dirname);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Common::Xml;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::S3::Request;
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oDriver,
$strName,
$bIgnoreMissing,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oDriver', trace => true},
{name => 'strName', trace => true},
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
);
# Open file
my $self = $oDriver->request(
HTTP_VERB_GET, {strUri => $strName, strResponseType => S3_RESPONSE_TYPE_IO, bIgnoreMissing => $bIgnoreMissing});
# Bless with new class if file exists
if (defined($self))
{
bless $self, $class;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self, trace => true}
);
}
####################################################################################################################################
# Getters
####################################################################################################################################
sub name {shift->{strName}}
1;

View File

@ -1,205 +0,0 @@
####################################################################################################################################
# S3 File Write
####################################################################################################################################
package pgBackRest::Storage::S3::FileWrite;
use parent 'pgBackRest::Common::Io::Base';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Digest::MD5 qw(md5_base64);
use Fcntl qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC);
use File::Basename qw(dirname);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Io::Handle;
use pgBackRest::Common::Log;
use pgBackRest::Common::Xml;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::S3::Request;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant S3_BUFFER_MAX => 16777216;
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oDriver,
$strName,
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oDriver', trace => true},
{name => 'strName', trace => true},
);
# Create the class hash
my $self = $class->SUPER::new("'${strName}'");
bless $self, $class;
# Set variables
$self->{oDriver} = $oDriver;
$self->{strName} = $strName;
# Start with an empty buffer
$self->{rtBuffer} = '';
# Has anything been written?
$self->{bWritten} = false;
# How much has been written?
$self->{lSize} = 0;
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self, trace => true}
);
}
####################################################################################################################################
# open - open the file
####################################################################################################################################
sub open
{
my $self = shift;
# Request an upload id
my $oResponse = $self->{oDriver}->request(
HTTP_VERB_POST, {strUri => $self->{strName}, hQuery => 'uploads=', strResponseType => S3_RESPONSE_TYPE_XML});
$self->{strUploadId} = xmlTagText($oResponse, 'UploadId');
# Intialize the multi-part array
$self->{rstryMultiPart} = [];
}
####################################################################################################################################
# write - write data to a file
####################################################################################################################################
sub write
{
my $self = shift;
my $rtBuffer = shift;
# Note that write has been called
$self->{bWritten} = true;
if (defined($rtBuffer))
{
$self->{rtBuffer} .= $$rtBuffer;
$self->{lSize} += length($$rtBuffer);
# Wait until buffer is at least max before writing to avoid writing smaller files multi-part
if (length($self->{rtBuffer}) >= S3_BUFFER_MAX)
{
$self->flush();
}
return length($$rtBuffer);
}
return 0;
}
####################################################################################################################################
# flush - flush whatever is in the buffer out
####################################################################################################################################
sub flush
{
my $self = shift;
# Open file if it is not open already
$self->open() if !$self->opened();
# Put a file
$self->{oDriver}->request(
HTTP_VERB_PUT,
{strUri => $self->{strName},
hQuery => {'partNumber' => @{$self->{rstryMultiPart}} + 1, 'uploadId' => $self->{strUploadId}},
rstrBody => \$self->{rtBuffer}, hHeader => {'content-md5' => md5_base64($self->{rtBuffer}) . '=='}});
# Store the returned etag
push(@{$self->{rstryMultiPart}}, $self->{oDriver}->{hResponseHeader}{&S3_HEADER_ETAG});
# Clear the buffer
$self->{rtBuffer} = '';
}
####################################################################################################################################
# close - close the file
####################################################################################################################################
sub close
{
my $self = shift;
# Only close if something was written
if ($self->{bWritten})
{
# Make sure close does not run again
$self->{bWritten} = false;
# If the file is open then multipart transfer has already started and must be completed
if ($self->opened())
{
# flush out whatever is in the buffer
$self->flush();
my $strXml = XML_HEADER . '<CompleteMultipartUpload>';
my $iPartNo = 0;
foreach my $strETag (@{$self->{rstryMultiPart}})
{
$iPartNo++;
$strXml .= "<Part><PartNumber>${iPartNo}</PartNumber><ETag>${strETag}</ETag></Part>";
}
$strXml .= '</CompleteMultipartUpload>';
# Finalize file
my $oResponse = $self->{oDriver}->request(
HTTP_VERB_POST,
{strUri => $self->{strName}, hQuery => {'uploadId' => $self->{strUploadId}},
rstrBody => \$strXml, hHeader => {'content-md5' => md5_base64($strXml) . '=='},
strResponseType => S3_RESPONSE_TYPE_XML});
}
# Else the file can be transmitted in one block
else
{
$self->{oDriver}->request(
HTTP_VERB_PUT,
{strUri => $self->{strName}, rstrBody => \$self->{rtBuffer},
hHeader => {'content-md5' => md5_base64($self->{rtBuffer}) . '=='}});
}
}
# This is how we report the write size back to the caller. It's a bit hokey -- results are based on the object name and in all
# other cases I/O passes through Io::Handle. It doesn't seem worth fixing since it could break things and this code is being
# migrated to C anyway.
$self->resultSet(COMMON_IO_HANDLE, $self->{lSize});
return true;
}
####################################################################################################################################
# Getters
####################################################################################################################################
sub opened {defined(shift->{strUploadId})}
sub name {shift->{strName}}
1;

View File

@ -1,48 +0,0 @@
####################################################################################################################################
# S3 File Info
####################################################################################################################################
package pgBackRest::Storage::S3::Info;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use pgBackRest::Common::Log;
####################################################################################################################################
# new
####################################################################################################################################
sub new
{
my $class = shift;
# Create the class hash
my $self = {};
bless $self, $class;
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{lSize},
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'lSize'},
);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# Getters
####################################################################################################################################
sub size {shift->{lSize}}
1;

View File

@ -1,264 +0,0 @@
####################################################################################################################################
# S3 Request
####################################################################################################################################
package pgBackRest::Storage::S3::Request;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use IO::Socket::SSL;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Http::Client;
use pgBackRest::Common::Http::Common;
use pgBackRest::Common::Io::Base;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Common::Xml;
use pgBackRest::LibC qw(:crypto);
use pgBackRest::Storage::S3::Auth;
####################################################################################################################################
# Constants
####################################################################################################################################
use constant HTTP_VERB_GET => 'GET';
push @EXPORT, qw(HTTP_VERB_GET);
use constant HTTP_VERB_POST => 'POST';
push @EXPORT, qw(HTTP_VERB_POST);
use constant HTTP_VERB_PUT => 'PUT';
push @EXPORT, qw(HTTP_VERB_PUT);
use constant S3_HEADER_CONTENT_LENGTH => 'content-length';
push @EXPORT, qw(S3_HEADER_CONTENT_LENGTH);
use constant S3_HEADER_TRANSFER_ENCODING => 'transfer-encoding';
push @EXPORT, qw(S3_HEADER_TRANSFER_ENCODING);
use constant S3_HEADER_ETAG => 'etag';
push @EXPORT, qw(S3_HEADER_ETAG);
use constant S3_RESPONSE_TYPE_IO => 'io';
push @EXPORT, qw(S3_RESPONSE_TYPE_IO);
use constant S3_RESPONSE_TYPE_NONE => 'none';
push @EXPORT, qw(S3_RESPONSE_TYPE_NONE);
use constant S3_RESPONSE_TYPE_XML => 'xml';
push @EXPORT, qw(S3_RESPONSE_TYPE_XML);
use constant S3_RESPONSE_CODE_SUCCESS => 200;
use constant S3_RESPONSE_CODE_ERROR_AUTH => 403;
use constant S3_RESPONSE_CODE_ERROR_NOT_FOUND => 404;
use constant S3_RESPONSE_CODE_ERROR_RETRY_CLASS => 5;
use constant S3_RETRY_MAX => 4;
####################################################################################################################################
# new
####################################################################################################################################
sub new
{
my $class = shift;
# Create the class hash
my $self = {};
bless $self, $class;
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{strBucket},
$self->{strEndPoint},
$self->{strRegion},
$self->{strAccessKeyId},
$self->{strSecretAccessKey},
$self->{strSecurityToken},
$self->{strHost},
$self->{iPort},
$self->{bVerifySsl},
$self->{strCaPath},
$self->{strCaFile},
$self->{lBufferMax},
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'strBucket'},
{name => 'strEndPoint'},
{name => 'strRegion'},
{name => 'strAccessKeyId', redact => true},
{name => 'strSecretAccessKey', redact => true},
{name => 'strSecurityToken', optional => true, redact => true},
{name => 'strHost', optional => true},
{name => 'iPort', optional => true},
{name => 'bVerifySsl', optional => true, default => true},
{name => 'strCaPath', optional => true},
{name => 'strCaFile', optional => true},
{name => 'lBufferMax', optional => true, default => COMMON_IO_BUFFER_MAX},
);
# If host is not set then it will be bucket + endpoint
$self->{strHost} = defined($self->{strHost}) ? $self->{strHost} : "$self->{strBucket}.$self->{strEndPoint}";
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self, trace => true}
);
}
####################################################################################################################################
# request - send a request to S3
####################################################################################################################################
sub request
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strVerb,
$strUri,
$hQuery,
$hHeader,
$rstrBody,
$strResponseType,
$bIgnoreMissing,
) =
logDebugParam
(
__PACKAGE__ . '->request', \@_,
{name => 'strVerb', trace => true},
{name => 'strUri', optional => true, default => '/', trace => true},
{name => 'hQuery', optional => true, trace => true},
{name => 'hHeader', optional => true, trace => true},
{name => 'rstrBody', optional => true, trace => true},
{name => 'strResponseType', optional => true, default => S3_RESPONSE_TYPE_NONE, trace => true},
{name => 'bIgnoreMissing', optional => true, default => false, trace => true},
);
# Server response
my $oResponse;
# Allow retries on S3 internal failures
my $bRetry;
my $iRetryTotal = 0;
do
{
# Assume that a retry will not be attempted which is true in most cases
$bRetry = false;
# Set content length and hash
$hHeader->{&S3_HEADER_CONTENT_SHA256} = defined($rstrBody) ? cryptoHashOne('sha256', $$rstrBody) : PAYLOAD_DEFAULT_HASH;
$hHeader->{&S3_HEADER_CONTENT_LENGTH} = defined($rstrBody) ? length($$rstrBody) : 0;
# Generate authorization header
($hHeader, my $strCanonicalRequest, my $strSignedHeaders, my $strStringToSign) = s3AuthorizationHeader(
$self->{strRegion}, "$self->{strBucket}.$self->{strEndPoint}", $strVerb, $strUri, httpQuery($hQuery), s3DateTime(),
$hHeader, $self->{strAccessKeyId}, $self->{strSecretAccessKey}, $self->{strSecurityToken},
$hHeader->{&S3_HEADER_CONTENT_SHA256});
# Send the request
my $oHttpClient = new pgBackRest::Common::Http::Client(
$self->{strHost}, $strVerb,
{iPort => $self->{iPort}, strUri => $strUri, hQuery => $hQuery, hRequestHeader => $hHeader,
rstrRequestBody => $rstrBody, bVerifySsl => $self->{bVerifySsl}, strCaPath => $self->{strCaPath},
strCaFile => $self->{strCaFile}, bResponseBodyPrefetch => $strResponseType eq S3_RESPONSE_TYPE_XML,
lBufferMax => $self->{lBufferMax}});
# Check response code
my $iResponseCode = $oHttpClient->responseCode();
if ($iResponseCode == S3_RESPONSE_CODE_SUCCESS)
{
# Save the response headers locally
$self->{hResponseHeader} = $oHttpClient->responseHeader();
# XML response is expected
if ($strResponseType eq S3_RESPONSE_TYPE_XML)
{
my $rtResponseBody = $oHttpClient->responseBody();
if ($oHttpClient->contentLength() == 0 || !defined($$rtResponseBody))
{
confess &log(ERROR,
"response type '${strResponseType}' was requested but content length is zero or content is missing",
ERROR_PROTOCOL);
}
$oResponse = xmlParse($$rtResponseBody);
}
# An IO object is expected for file responses
elsif ($strResponseType eq S3_RESPONSE_TYPE_IO)
{
$oResponse = $oHttpClient;
}
}
else
{
# If file was not found
if ($iResponseCode == S3_RESPONSE_CODE_ERROR_NOT_FOUND)
{
# If missing files should not be ignored then error
if (!$bIgnoreMissing)
{
confess &log(ERROR, "unable to open '${strUri}': No such file or directory", ERROR_FILE_MISSING);
}
$bRetry = false;
}
# Else a more serious error
else
{
# Retry for S3 internal or rate-limiting errors (any 5xx error should be retried)
if (int($iResponseCode / 100) == S3_RESPONSE_CODE_ERROR_RETRY_CLASS)
{
# Increment retry total and check if retry should be attempted
$iRetryTotal++;
$bRetry = $iRetryTotal <= S3_RETRY_MAX;
# Sleep after first retry just in case data needs to stabilize
if ($iRetryTotal > 1)
{
sleep(5);
}
}
# If no retry then throw the error
if (!$bRetry)
{
my $rstrResponseBody = $oHttpClient->responseBody();
# Redact authorization header because it contains the access key
my $strRequestHeader = $oHttpClient->requestHeaderText();
$strRequestHeader =~ s/^${\S3_HEADER_AUTHORIZATION}:.*$/${\S3_HEADER_AUTHORIZATION}: <redacted>/mg;
confess &log(ERROR,
'S3 request error' . ($iRetryTotal > 0 ? " after " . (S3_RETRY_MAX + 1) . " tries" : '') .
" [$iResponseCode] " . $oHttpClient->responseMessage() .
"\n*** request header ***\n${strRequestHeader}" .
($iResponseCode == S3_RESPONSE_CODE_ERROR_AUTH ?
"\n*** canonical request ***\n" . $strCanonicalRequest .
"\n*** signed headers ***\n" . $strSignedHeaders .
"\n*** string to sign ***\n" . $strStringToSign : '') .
"\n*** response header ***\n" . $oHttpClient->responseHeaderText() .
(defined($$rstrResponseBody) ? "\n*** response body ***\n${$rstrResponseBody}" : ''),
ERROR_PROTOCOL);
}
}
}
}
while ($bRetry);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'oResponse', value => $oResponse, trace => true, ref => true}
);
}
1;

View File

@ -0,0 +1,190 @@
####################################################################################################################################
# C Storage Read Interface
####################################################################################################################################
package pgBackRest::Storage::StorageRead;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use File::Basename qw(dirname);
use Fcntl qw(:mode);
use File::stat qw{lstat};
use JSON::PP;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Storage::Base;
####################################################################################################################################
# new
####################################################################################################################################
sub new
{
my $class = shift;
# Create the class hash
my $self = {};
bless $self, $class;
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{oStorage},
$self->{oStorageCRead},
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oStorage'},
{name => 'oStorageCRead'},
);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# Open the file
####################################################################################################################################
sub open
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->open');
return logDebugReturn
(
$strOperation,
{name => 'bResult', value => $self->{oStorageCRead}->open() ? true : false, trace => true},
);
}
####################################################################################################################################
# Read data
####################################################################################################################################
sub read
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my (
$strOperation,
$rtBuffer,
$iSize,
) =
logDebugParam
(
__PACKAGE__ . '->read', \@_,
{name => 'rtBuffer'},
{name => 'iSize'},
);
# Read if not eof
my $iActualSize = 0;
if (!$self->eof())
{
my $tBuffer = $self->{oStorageCRead}->read($iSize);
$iActualSize = length($tBuffer);
$$rtBuffer .= $tBuffer;
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'iActualSize', value => $iActualSize}
);
}
####################################################################################################################################
# Is the file at eof?
####################################################################################################################################
sub eof
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->eof');
return logDebugReturn
(
$strOperation,
{name => 'bResult', value => $self->{oStorageCRead}->eof() ? true : false, trace => true},
);
}
####################################################################################################################################
# Close the file
####################################################################################################################################
sub close
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->close');
$self->{oStorageCRead}->close();
return logDebugReturn
(
$strOperation,
{name => 'bResult', value => true, trace => true},
);
}
####################################################################################################################################
# Get a filter result
####################################################################################################################################
sub result
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strClass,
) =
logDebugParam
(
__PACKAGE__ . '->result', \@_,
{name => 'strClass'},
);
my $xResult = $self->{oStorage}->{oJSON}->decode($self->{oStorageCRead}->result($strClass));
return logDebugReturn
(
$strOperation,
{name => 'xResult', value => $xResult, trace => true},
);
}
####################################################################################################################################
# Get all filter results
####################################################################################################################################
sub resultAll
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->resultAll');
my $xResult = $self->{oStorage}->{oJSON}->decode($self->{oStorageCRead}->resultAll());
return logDebugReturn
(
$strOperation,
{name => 'xResultAll', value => $xResult, trace => true},
);
}
1;

View File

@ -0,0 +1,163 @@
####################################################################################################################################
# C Storage Write Interface
####################################################################################################################################
package pgBackRest::Storage::StorageWrite;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use File::Basename qw(dirname);
use Fcntl qw(:mode);
use File::stat qw{lstat};
use JSON::PP;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Storage::Base;
####################################################################################################################################
# new
####################################################################################################################################
sub new
{
my $class = shift;
# Create the class hash
my $self = {};
bless $self, $class;
# Assign function parameters, defaults, and log debug info
(
my $strOperation,
$self->{oStorage},
$self->{oStorageCWrite},
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'oStorage'},
{name => 'oStorageCWrite'},
);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# Open the file
####################################################################################################################################
sub open
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->open');
$self->{oStorageCWrite}->open();
return logDebugReturn
(
$strOperation,
{name => 'bResult', value => true, trace => true},
);
}
####################################################################################################################################
# Write data
####################################################################################################################################
sub write
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my (
$strOperation,
$rtBuffer,
) =
logDebugParam
(
__PACKAGE__ . '->write', \@_,
{name => 'rtBuffer'},
);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'iActualSize', value => $self->{oStorageCWrite}->write($$rtBuffer)}
);
}
####################################################################################################################################
# Close the file
####################################################################################################################################
sub close
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->close');
$self->{oStorageCWrite}->close();
return logDebugReturn
(
$strOperation,
{name => 'bResult', value => true, trace => true},
);
}
####################################################################################################################################
# Get a filter result
####################################################################################################################################
sub result
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strClass,
) =
logDebugParam
(
__PACKAGE__ . '->result', \@_,
{name => 'strClass'},
);
my $xResult = $self->{oStorage}->{oJSON}->decode($self->{oStorageCWrite}->result($strClass));
return logDebugReturn
(
$strOperation,
{name => 'xResult', value => $xResult, trace => true},
);
}
####################################################################################################################################
# Get all filter results
####################################################################################################################################
sub resultAll
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->resultAll');
my $xResult = $self->{oStorage}->{oJSON}->decode($self->{oStorageCWrite}->resultAll());
return logDebugReturn
(
$strOperation,
{name => 'xResultAll', value => $xResult, trace => true},
);
}
1;

View File

@ -181,3 +181,15 @@ Free memory context in destructors
***********************************************************************************************************************************/
#define MEM_CONTEXT_XS_DESTROY(memContext) \
memContextFree(memContext)
/***********************************************************************************************************************************
Create new string from an SV
***********************************************************************************************************************************/
#define STR_NEW_SV(param) \
(SvOK(param) ? strNewN(SvPV_nolen(param), SvCUR(param)) : NULL)
/***********************************************************************************************************************************
Create const buffer from an SV
***********************************************************************************************************************************/
#define BUF_CONST_SV(param) \
(SvOK(param) ? BUF(SvPV_nolen(param), SvCUR(param)) : NULL)

View File

@ -50,6 +50,7 @@ These includes are from the src directory. There is no Perl-specific code in th
***********************************************************************************************************************************/
#include "common/crypto/common.h"
#include "common/error.h"
#include "common/io/io.h"
#include "common/lock.h"
#include "config/config.h"
#include "config/define.h"
@ -69,9 +70,11 @@ XSH includes
These includes define data structures that are required for the C to Perl interface but are not part of the regular C source.
***********************************************************************************************************************************/
#include "xs/crypto/cipherBlock.xsh"
#include "xs/crypto/hash.xsh"
#include "xs/common/encode.xsh"
#include "xs/storage/storage.xsh"
#include "xs/storage/storageRead.xsh"
#include "xs/storage/storageWrite.xsh"
/***********************************************************************************************************************************
Module definition
@ -97,8 +100,9 @@ INCLUDE: xs/common/lock.xs
INCLUDE: xs/config/config.xs
INCLUDE: xs/config/configTest.xs
INCLUDE: xs/config/define.xs
INCLUDE: xs/crypto/cipherBlock.xs
INCLUDE: xs/crypto/hash.xs
INCLUDE: xs/crypto/random.xs
INCLUDE: xs/postgres/pageChecksum.xs
INCLUDE: xs/storage/storage.xs
INCLUDE: xs/storage/storageRead.xs
INCLUDE: xs/storage/storageWrite.xs

View File

@ -42,6 +42,7 @@ my @stryCFile =
(
'LibC.c',
'command/backup/pageChecksum.c',
'command/command.c',
'common/compress/gzip/common.c',
'common/compress/gzip/compress.c',
@ -61,8 +62,14 @@ my @stryCFile =
'common/io/filter/group.c',
'common/io/filter/size.c',
'common/io/handleWrite.c',
'common/io/http/cache.c',
'common/io/http/client.c',
'common/io/http/common.c',
'common/io/http/header.c',
'common/io/http/query.c',
'common/io/io.c',
'common/io/read.c',
'common/io/tls/client.c',
'common/io/write.c',
'common/lock.c',
'common/log.c',
@ -86,10 +93,19 @@ my @stryCFile =
'config/load.c',
'config/parse.c',
'perl/config.c',
'protocol/client.c',
'protocol/command.c',
'protocol/helper.c',
'protocol/parallel.c',
'protocol/parallelJob.c',
'protocol/server.c',
'postgres/pageChecksum.c',
'storage/posix/read.c',
'storage/posix/storage.c',
'storage/posix/write.c',
'storage/s3/read.c',
'storage/s3/storage.c',
'storage/s3/write.c',
'storage/helper.c',
'storage/read.c',
'storage/storage.c',

View File

@ -19,8 +19,6 @@ use lib dirname($0) . '/../lib';
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Storage::Local;
use pgBackRest::Storage::Posix::Driver;
use pgBackRest::Version;
use pgBackRestBuild::Build;
@ -28,6 +26,9 @@ use pgBackRestBuild::Build::Common;
use pgBackRestBuild::Config::Data;
use pgBackRestBuild::Error::Data;
use pgBackRestTest::Common::Storage;
use pgBackRestTest::Common::StoragePosix;
####################################################################################################################################
# Perl function and constant exports
####################################################################################################################################
@ -74,8 +75,6 @@ my $rhExport =
{
&BLD_EXPORTTYPE_SUB => [qw(
pageChecksum
pageChecksumBufferTest
pageChecksumTest
)],
},
@ -149,7 +148,7 @@ my $rhExport =
'storage' =>
{
&BLD_EXPORTTYPE_SUB => [qw(
storagePosixPathRemove
storageRepoFree
)],
},
@ -172,8 +171,8 @@ sub buildXsAll
my @stryBuilt;
# Storage
my $oStorage = new pgBackRest::Storage::Local(
$strBuildPath, new pgBackRest::Storage::Posix::Driver({bFileSync => false, bPathSync => false}));
my $oStorage = new pgBackRestTest::Common::Storage(
$strBuildPath, new pgBackRestTest::Common::StoragePosix({bFileSync => false, bPathSync => false}));
# Build interface file
my $strContent =

View File

@ -1,2 +1,3 @@
pgBackRest::LibC::Cipher::Block T_PTROBJ
pgBackRest::LibC::Crypto::Hash T_PTROBJ
pgBackRest::LibC::Storage T_PTROBJ
pgBackRest::LibC::StorageRead T_PTROBJ
pgBackRest::LibC::StorageWrite T_PTROBJ

View File

@ -1,114 +0,0 @@
####################################################################################################################################
# Block Cipher Perl Exports
#
# XS wrapper for functions in cipher/block.c.
####################################################################################################################################
MODULE = pgBackRest::LibC PACKAGE = pgBackRest::LibC::Cipher::Block
####################################################################################################################################
pgBackRest::LibC::Cipher::Block
new(class, mode, type, key, keySize, digest = NULL)
const char *class
U32 mode
const char *type
unsigned char *key
I32 keySize
const char *digest
CODE:
RETVAL = NULL;
CHECK(type != NULL);
CHECK(key != NULL);
CHECK(keySize != 0);
// Not much point to this but it keeps the var from being unused
if (strcmp(class, PACKAGE_NAME_LIBC "::Cipher::Block") != 0)
croak("unexpected class name '%s'", class);
MEM_CONTEXT_XS_NEW_BEGIN("cipherBlockXs")
{
RETVAL = memNew(sizeof(CipherBlockXs));
RETVAL->memContext = MEM_CONTEXT_XS();
RETVAL->pxPayload = cipherBlockNew(mode, cipherType(STR(type)), BUF(key, keySize), digest == NULL ? NULL : STR(digest));
}
MEM_CONTEXT_XS_NEW_END();
OUTPUT:
RETVAL
####################################################################################################################################
SV *
process(self, source)
pgBackRest::LibC::Cipher::Block self
SV *source
CODE:
RETVAL = NULL;
MEM_CONTEXT_XS_BEGIN(self->memContext)
{
STRLEN tSize;
const unsigned char *sourcePtr = (const unsigned char *)SvPV(source, tSize);
RETVAL = NEWSV(0, ioBufferSize());
SvPOK_only(RETVAL);
if (tSize > 0)
{
size_t outBufferUsed = 0;
do
{
SvGROW(RETVAL, outBufferUsed + ioBufferSize());
Buffer *outBuffer = bufNewUseC((unsigned char *)SvPV_nolen(RETVAL) + outBufferUsed, ioBufferSize());
ioFilterProcessInOut(self->pxPayload, BUF(sourcePtr, tSize), outBuffer);
outBufferUsed += bufUsed(outBuffer);
}
while (ioFilterInputSame(self->pxPayload));
SvCUR_set(RETVAL, outBufferUsed);
}
else
SvCUR_set(RETVAL, 0);
}
MEM_CONTEXT_XS_END();
OUTPUT:
RETVAL
####################################################################################################################################
SV *
flush(self)
pgBackRest::LibC::Cipher::Block self
CODE:
RETVAL = NULL;
MEM_CONTEXT_XS_BEGIN(self->memContext)
{
RETVAL = NEWSV(0, ioBufferSize());
SvPOK_only(RETVAL);
size_t outBufferUsed = 0;
do
{
SvGROW(RETVAL, outBufferUsed + ioBufferSize());
Buffer *outBuffer = bufNewUseC((unsigned char *)SvPV_nolen(RETVAL) + outBufferUsed, ioBufferSize());
ioFilterProcessInOut(self->pxPayload, NULL, outBuffer);
outBufferUsed += bufUsed(outBuffer);
}
while (!ioFilterDone(self->pxPayload));
SvCUR_set(RETVAL, outBufferUsed);
}
MEM_CONTEXT_XS_END();
OUTPUT:
RETVAL
####################################################################################################################################
void
DESTROY(self)
pgBackRest::LibC::Cipher::Block self
CODE:
MEM_CONTEXT_XS_DESTROY(self->memContext);

View File

@ -1,17 +0,0 @@
/***********************************************************************************************************************************
Block Cipher XS Header
***********************************************************************************************************************************/
#include "common/assert.h"
#include "common/crypto/cipherBlock.h"
#include "common/io/io.h"
#include "common/memContext.h"
// Encrypt/decrypt modes
#define CIPHER_MODE_ENCRYPT ((int)cipherModeEncrypt)
#define CIPHER_MODE_DECRYPT ((int)cipherModeDecrypt)
typedef struct CipherBlockXs
{
MemContext *memContext;
IoFilter *pxPayload;
} CipherBlockXs, *pgBackRest__LibC__Cipher__Block;

View File

@ -4,72 +4,6 @@
# XS wrapper for functions in cipher/hash.c.
####################################################################################################################################
MODULE = pgBackRest::LibC PACKAGE = pgBackRest::LibC::Crypto::Hash
####################################################################################################################################
pgBackRest::LibC::Crypto::Hash
new(class, type)
const char *class
const char *type
CODE:
RETVAL = NULL;
// Don't warn when class param is used
(void)class;
MEM_CONTEXT_XS_NEW_BEGIN("cryptoHashXs")
{
RETVAL = memNew(sizeof(CryptoHashXs));
RETVAL->memContext = MEM_CONTEXT_XS();
RETVAL->pxPayload = cryptoHashNew(strNew(type));
}
MEM_CONTEXT_XS_NEW_END();
OUTPUT:
RETVAL
####################################################################################################################################
void
process(self, message)
pgBackRest::LibC::Crypto::Hash self
SV *message
CODE:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
STRLEN messageSize;
const void *messagePtr = SvPV(message, messageSize);
if (messageSize > 0)
ioFilterProcessIn(self->pxPayload, BUF(messagePtr, messageSize));
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
SV *
result(self)
pgBackRest::LibC::Crypto::Hash self
CODE:
RETVAL = NULL;
MEM_CONTEXT_XS_TEMP_BEGIN()
{
const String *hash = varStr(ioFilterResult(self->pxPayload));
RETVAL = newSV(strSize(hash));
SvPOK_only(RETVAL);
strcpy((char *)SvPV_nolen(RETVAL), strPtr(hash));
SvCUR_set(RETVAL, strSize(hash));
}
MEM_CONTEXT_XS_TEMP_END();
OUTPUT:
RETVAL
####################################################################################################################################
void
DESTROY(self)
pgBackRest::LibC::Crypto::Hash self
CODE:
MEM_CONTEXT_XS_DESTROY(self->memContext);
MODULE = pgBackRest::LibC PACKAGE = pgBackRest::LibC
####################################################################################################################################

View File

@ -2,11 +2,3 @@
Cryptographic Hashes XS Header
***********************************************************************************************************************************/
#include "common/crypto/hash.h"
#include "common/io/filter/filter.intern.h"
#include "common/memContext.h"
typedef struct CryptoHashXs
{
MemContext *memContext;
IoFilter *pxPayload;
} CryptoHashXs, *pgBackRest__LibC__Crypto__Hash;

View File

@ -20,42 +20,3 @@ CODE:
ERROR_XS_END();
OUTPUT:
RETVAL
bool
pageChecksumTest(page, blockNo, pageSize, ignoreWalId, ignoreWalOffset)
const char *page
U32 blockNo
U32 pageSize
U32 ignoreWalId
U32 ignoreWalOffset
CODE:
RETVAL = false;
ERROR_XS_BEGIN()
{
RETVAL = pageChecksumTest(
(const unsigned char *)page, blockNo, pageSize, ignoreWalId, ignoreWalOffset);
}
ERROR_XS_END();
OUTPUT:
RETVAL
bool
pageChecksumBufferTest(pageBuffer, pageBufferSize, blockNoBegin, pageSize, ignoreWalId, ignoreWalOffset)
const char *pageBuffer
U32 pageBufferSize
U32 blockNoBegin
U32 pageSize
U32 ignoreWalId
U32 ignoreWalOffset
CODE:
RETVAL = false;
ERROR_XS_BEGIN()
{
RETVAL = pageChecksumBufferTest(
(const unsigned char *)pageBuffer, pageBufferSize, blockNoBegin, pageSize, ignoreWalId, ignoreWalOffset);
}
ERROR_XS_END();
OUTPUT:
RETVAL

View File

@ -2,19 +2,409 @@
# Storage Exports
# ----------------------------------------------------------------------------------------------------------------------------------
MODULE = pgBackRest::LibC PACKAGE = pgBackRest::LibC::Storage
####################################################################################################################################
pgBackRest::LibC::Storage
new(class, type, path)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
const String *class = STR_NEW_SV($arg);
const String *type = STR_NEW_SV($arg);
const String *path = STR_NEW_SV($arg);
CODE:
CHECK(strEqZ(class, PACKAGE_NAME_LIBC "::Storage"));
if (strEqZ(type, "<LOCAL>"))
{
memContextSwitch(MEM_CONTEXT_XS_OLD());
RETVAL = storagePosixNew(
path == NULL ? STRDEF("/") : path, STORAGE_MODE_FILE_DEFAULT, STORAGE_MODE_PATH_DEFAULT, true, NULL);
storagePathEnforceSet((Storage *)RETVAL, false);
memContextSwitch(MEM_CONTEXT_XS_TEMP());
}
else if (strEqZ(type, "<REPO>"))
{
CHECK(path == NULL);
RETVAL = (Storage *)storageRepoWrite();
}
else if (strEqZ(type, "<DB>"))
{
CHECK(path == NULL);
memContextSwitch(MEM_CONTEXT_XS_OLD());
RETVAL = storagePosixNew(cfgOptionStr(cfgOptPgPath), STORAGE_MODE_FILE_DEFAULT, STORAGE_MODE_PATH_DEFAULT, true, NULL);
storagePathEnforceSet((Storage *)RETVAL, false);
memContextSwitch(MEM_CONTEXT_XS_TEMP());
}
else
THROW_FMT(AssertError, "unexpected storage type '%s'", strPtr(type));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
bool
copy(self, source, destination)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead source
pgBackRest::LibC::StorageWrite destination
CODE:
RETVAL = storageCopyNP(source, destination);
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
bool
exists(self, fileExp)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *fileExp = STR_NEW_SV($arg);
CODE:
RETVAL = storageExistsNP(self, fileExp);
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
SV *
get(self, read)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead read
CODE:
RETVAL = NULL;
Buffer *buffer = storageGetNP(read);
if (buffer != NULL)
{
if (bufUsed(buffer) == 0)
RETVAL = newSVpv("", 0);
else
RETVAL = newSVpv((char *)bufPtr(buffer), bufUsed(buffer));
}
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
SV *
info(self, pathExp, ignoreMissing)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *pathExp = STR_NEW_SV($arg);
bool ignoreMissing
CODE:
RETVAL = NULL;
StorageInfo info = storageInfoP(self, pathExp, .ignoreMissing = ignoreMissing);
if (info.exists)
{
String *json = storageManifestXsInfo(NULL, &info);
RETVAL = newSVpv((char *)strPtr(json), strSize(json));
}
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
SV *
list(self, pathExp, ignoreMissing, sortAsc, expression)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *pathExp = STR_NEW_SV($arg);
bool ignoreMissing
bool sortAsc
const String *expression = STR_NEW_SV($arg);
CODE:
StringList *fileList = strLstSort(
storageListP(self, pathExp, .errorOnMissing = storageFeature(self, storageFeaturePath) ? !ignoreMissing : false,
.expression = expression), sortAsc ? sortOrderAsc : sortOrderDesc);
const String *fileListJson = jsonFromVar(varNewVarLst(varLstNewStrLst(fileList)), 0);
RETVAL = newSVpv(strPtr(fileListJson), strSize(fileListJson));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
SV *
manifest(self, pathExp, filter=NULL)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *pathExp = STR_NEW_SV($arg);
const String *filter = STR_NEW_SV($arg);
CODE:
StorageManifestXsCallbackData data = {.storage = self, .json = strNew("{"), .pathRoot = pathExp, .filter = filter};
// If a path is specified
StorageInfo info = storageInfoP(self, pathExp, .ignoreMissing = true);
if (!info.exists || info.type == storageTypePath)
{
storageInfoListP(
self, data.pathRoot, storageManifestXsCallback, &data,
.errorOnMissing = storageFeature(self, storageFeaturePath) ? true : false);
}
// Else a file is specified
else
{
info.name = strBase(storagePath(self, pathExp));
strCat(data.json, strPtr(storageManifestXsInfo(NULL, &info)));
}
strCat(data.json, "}");
RETVAL = newSVpv((char *)strPtr(data.json), strSize(data.json));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
pathCreate(self, pathExp, mode, ignoreExists, createParent)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *pathExp = STR_NEW_SV($arg);
const String *mode = STR_NEW_SV($arg);
bool ignoreExists
bool createParent
CODE:
if (storageFeature(self, storageFeaturePath))
storagePathCreateP(
self, pathExp, .mode = mode ? cvtZToIntBase(strPtr(mode), 8) : 0, .errorOnExists = !ignoreExists,
.noParentCreate = !createParent);
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
bool
pathExists(self, pathExp)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *pathExp = STR_NEW_SV($arg);
CODE:
RETVAL = true;
if (storageFeature(self, storageFeaturePath))
RETVAL = storagePathExistsNP(self, pathExp);
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
SV *
pathGet(self, pathExp)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *pathExp = STR_NEW_SV($arg);
CODE:
String *path = storagePathNP(self, pathExp);
RETVAL = newSVpv((char *)strPtr(path), strSize(path));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
pathRemove(self, pathExp, ignoreMissing, recurse)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *pathExp = STR_NEW_SV($arg);
bool ignoreMissing
bool recurse
CODE:
storagePathRemoveP(
self, pathExp, .errorOnMissing = storageFeature(self, storageFeaturePath) ? !ignoreMissing : false, .recurse = recurse);
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
pathSync(self, pathExp)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *pathExp = STR_NEW_SV($arg);
CODE:
storagePathSyncNP(self, pathExp);
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
UV
put(self, write, buffer)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageWrite write
const Buffer *buffer = BUF_CONST_SV($arg);
CODE:
storagePutNP(write, buffer);
RETVAL = buffer ? bufUsed(buffer) : 0;
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
bool
readDrain(self, read)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead read
CODE:
RETVAL = false;
// Read and discard all IO (this is useful for processing filters)
if (ioReadOpen(storageReadIo(read)))
{
Buffer *buffer = bufNew(ioBufferSize());
do
{
ioRead(storageReadIo(read), buffer);
bufUsedZero(buffer);
}
while (!ioReadEof(storageReadIo(read)));
ioReadClose(storageReadIo(read));
RETVAL = true;
}
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
remove(self, fileExp, ignoreMissing)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
const String *fileExp = STR_NEW_SV($arg);
bool ignoreMissing
CODE:
storageRemoveP(self, fileExp, .errorOnMissing = storageFeature(self, storageFeaturePath) ? !ignoreMissing : false);
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
const char *
cipherType(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
CODE:
if (cfgOptionStr(cfgOptRepoCipherType) == NULL || cipherType(cfgOptionStr(cfgOptRepoCipherType)) == cipherTypeNone)
RETVAL = NULL;
else
RETVAL = strPtr(cfgOptionStr(cfgOptRepoCipherType));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
const char *
cipherPass(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
CODE:
RETVAL = strPtr(cfgOptionStr(cfgOptRepoCipherPass));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
const char *
type(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::Storage self
CODE:
RETVAL = strPtr(storageType(self));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
MODULE = pgBackRest::LibC PACKAGE = pgBackRest::LibC
####################################################################################################################################
void
storagePosixPathRemove(path, errorOnMissing, recurse)
const char *path
bool errorOnMissing
bool recurse
storageRepoFree()
CODE:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
storagePathRemoveP(
storagePosixNew(strNew("/"), 0640, 750, true, NULL), strNew(path), .errorOnMissing = errorOnMissing,
.recurse = recurse);
}
MEM_CONTEXT_XS_TEMP_END();
storageHelperFree();

222
libc/xs/storage/storage.xsh Normal file
View File

@ -0,0 +1,222 @@
/***********************************************************************************************************************************
Storage XS Header
***********************************************************************************************************************************/
#include "command/backup/pageChecksum.h"
#include "common/assert.h"
#include "common/compress/gzip/compress.h"
#include "common/compress/gzip/decompress.h"
#include "common/crypto/cipherBlock.h"
#include "common/io/filter/size.h"
#include "common/memContext.h"
#include "common/type/convert.h"
#include "common/type/json.h"
#include "postgres/interface.h"
#include "storage/helper.h"
#include "storage/storage.intern.h"
typedef Storage *pgBackRest__LibC__Storage;
/***********************************************************************************************************************************
Manifest callback
***********************************************************************************************************************************/
typedef struct StorageManifestXsCallbackData
{
const Storage *storage;
const String *pathRoot;
const String *path;
String *json;
const String *filter;
} StorageManifestXsCallbackData;
String *
storageManifestXsInfo(const String *path, const StorageInfo *info)
{
String *json = strNew("");
if (info->name != NULL)
{
strCatFmt(
json, "%s:", strPtr(jsonFromStr(path == NULL ? info->name : strNewFmt("%s/%s", strPtr(path), strPtr(info->name)))));
}
strCatFmt(json, "{\"group\":%s,\"user\":%s,\"type\":\"", strPtr(jsonFromStr(info->group)), strPtr(jsonFromStr(info->user)));
switch (info->type)
{
case storageTypeFile:
{
strCatFmt(
json, "f\",\"mode\":\"%04o\",\"modification_time\":%" PRId64 ",\"size\":%" PRIu64 "}", info->mode,
(int64_t)info->timeModified, info->size);
break;
}
case storageTypeLink:
{
strCatFmt(json, "l\",\"link_destination\":%s}", strPtr(jsonFromStr(info->linkDestination)));
break;
}
case storageTypePath:
{
strCatFmt(json, "d\",\"mode\":\"%04o\"}", info->mode);
break;
}
}
return json;
}
void
storageManifestXsCallback(void *callbackData, const StorageInfo *info)
{
StorageManifestXsCallbackData *data = (StorageManifestXsCallbackData *)callbackData;
if (data->path == NULL || !strEqZ(info->name, "."))
{
if (!strEqZ(info->name, ".") && data->filter && !strEq(data->filter, info->name))
return;
if (strSize(data->json) != 1)
strCat(data->json, ",");
strCat(data->json, strPtr(storageManifestXsInfo(data->path, info)));
if (info->type == storageTypePath)
{
if (!strEqZ(info->name, "."))
{
StorageManifestXsCallbackData dataSub =
{
.storage = data->storage,
.json = data->json,
.pathRoot = data->pathRoot,
.path = data->path == NULL ? info->name : strNewFmt("%s/%s", strPtr(data->path), strPtr(info->name)),
};
storageInfoListNP(
dataSub.storage, strNewFmt("%s/%s", strPtr(dataSub.pathRoot), strPtr(dataSub.path)), storageManifestXsCallback,
&dataSub);
}
}
}
}
/***********************************************************************************************************************************
Add IO filter
***********************************************************************************************************************************/
void
storageFilterXsAdd(IoFilterGroup *filterGroup, const String *filter, const String *paramJson)
{
VariantList *paramList = paramJson ? jsonToVarLst(paramJson) : NULL;
if (strEqZ(filter, "pgBackRest::Storage::Filter::CipherBlock"))
{
ioFilterGroupAdd(
filterGroup,
cipherBlockNew(
varUInt64Force(varLstGet(paramList, 0)) ? cipherModeEncrypt : cipherModeDecrypt,
cipherType(varStr(varLstGet(paramList, 1))), BUFSTR(varStr(varLstGet(paramList, 2))), NULL));
}
else if (strEqZ(filter, "pgBackRest::Storage::Filter::Sha"))
{
ioFilterGroupAdd(filterGroup, cryptoHashNew(HASH_TYPE_SHA1_STR));
}
else if (strEqZ(filter, "pgBackRest::Common::Io::Handle"))
{
ioFilterGroupAdd(filterGroup, ioSizeNew());
}
else if (strEqZ(filter, "pgBackRest::Backup::Filter::PageChecksum"))
{
ioFilterGroupAdd(
filterGroup,
pageChecksumNew(
varUInt64Force(varLstGet(paramList, 0)), PG_SEGMENT_PAGE_DEFAULT, PG_PAGE_SIZE_DEFAULT,
(uint64_t)varUIntForce(varLstGet(paramList, 1)) << 32 | varUIntForce(varLstGet(paramList, 2))));
}
else if (strEqZ(filter, "pgBackRest::Storage::Filter::Gzip"))
{
if (strEqZ(varStr(varLstGet(paramList, 0)), "compress"))
{
ioFilterGroupAdd(
filterGroup, gzipCompressNew(varUIntForce(varLstGet(paramList, 2)), varBoolForce(varLstGet(paramList, 1))));
}
else
{
ioFilterGroupAdd(filterGroup, gzipDecompressNew(varBoolForce(varLstGet(paramList, 1))));
}
}
else
THROW_FMT(AssertError, "unable to add invalid filter '%s'", strPtr(filter));
}
/***********************************************************************************************************************************
Get result from IO filter
***********************************************************************************************************************************/
String *
storageFilterXsResult(const IoFilterGroup *filterGroup, const String *filter)
{
const Variant *result;
if (strEqZ(filter, "pgBackRest::Storage::Filter::Sha"))
{
result = ioFilterGroupResult(filterGroup, CRYPTO_HASH_FILTER_TYPE_STR);
}
else if (strEqZ(filter, "pgBackRest::Common::Io::Handle"))
{
result = ioFilterGroupResult(filterGroup, SIZE_FILTER_TYPE_STR);
}
else if (strEqZ(filter, "pgBackRest::Backup::Filter::PageChecksum"))
{
result = ioFilterGroupResult(filterGroup, PAGE_CHECKSUM_FILTER_TYPE_STR);
}
else
THROW_FMT(AssertError, "unable to get result for invalid filter '%s'", strPtr(filter));
if (result == NULL)
THROW_FMT(AssertError, "unable to find result for filter '%s'", strPtr(filter));
return jsonFromVar(result, 0);
}
/***********************************************************************************************************************************
Get results from all IO filters
***********************************************************************************************************************************/
String *
storageFilterXsResultAll(const IoFilterGroup *filterGroup)
{
const VariantList *filterList = kvKeyList(varKv(ioFilterGroupResultAll(filterGroup)));
String *result = strNew("{");
for (unsigned int filterIdx = 0; filterIdx < varLstSize(filterList); filterIdx++)
{
const String *filter = varStr(varLstGet(filterList, filterIdx));
const String *filterPerl = NULL;
if (strEq(filter, CRYPTO_HASH_FILTER_TYPE_STR))
{
filterPerl = STRDEF("pgBackRest::Storage::Filter::Sha");
}
else if (strEq(filter, SIZE_FILTER_TYPE_STR))
{
filterPerl = STRDEF("pgBackRest::Common::Io::Handle");
}
else if (strEq(filter, PAGE_CHECKSUM_FILTER_TYPE_STR))
{
filterPerl = STRDEF("pgBackRest::Backup::Filter::PageChecksum");
}
if (filterPerl != NULL)
{
if (strSize(result) > 1)
strCat(result, ",");
strCatFmt(
result, "%s:%s", strPtr(jsonFromStr(filterPerl)), strPtr(storageFilterXsResult(filterGroup, filterPerl)));
}
}
strCat(result, "}");
return result;
}

View File

@ -0,0 +1,159 @@
# ----------------------------------------------------------------------------------------------------------------------------------
# Storage Read Exports
# ----------------------------------------------------------------------------------------------------------------------------------
MODULE = pgBackRest::LibC PACKAGE = pgBackRest::LibC::StorageRead
####################################################################################################################################
pgBackRest::LibC::StorageRead
new(class, storage, file, ignoreMissing)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
const String *class = STR_NEW_SV($arg);
pgBackRest::LibC::Storage storage
const String *file = STR_NEW_SV($arg);
bool ignoreMissing
CODE:
CHECK(strEqZ(class, PACKAGE_NAME_LIBC "::StorageRead"));
RETVAL = storageReadMove(storageNewReadP(storage, file, .ignoreMissing = ignoreMissing), MEM_CONTEXT_XS_OLD());
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
filterAdd(self, filter, param)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead self
const String *filter = STR_NEW_SV($arg);
const String *param = STR_NEW_SV($arg);
CODE:
IoFilterGroup *filterGroup = ioReadFilterGroup(storageReadIo(self));
storageFilterXsAdd(filterGroup, filter, param);
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
bool
open(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead self
CODE:
RETVAL = ioReadOpen(storageReadIo(self));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
SV *
read(self, bufferSize)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead self
U32 bufferSize
CODE:
RETVAL = NEWSV(0, bufferSize);
SvPOK_only(RETVAL);
Buffer *bufferRead = bufNewUseC((unsigned char *)SvPV_nolen(RETVAL), bufferSize);
ioRead(storageReadIo(self), bufferRead);
SvCUR_set(RETVAL, bufUsed(bufferRead));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
bool
eof(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead self
CODE:
RETVAL = ioReadEof(storageReadIo(self));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
close(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead self
CODE:
ioReadClose(storageReadIo(self));
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
const char *
result(self, filter)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead self
const String *filter = STR_NEW_SV($arg);
CODE:
RETVAL = strPtr(storageFilterXsResult(ioReadFilterGroup(storageReadIo(self)), filter));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
const char *
resultAll(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead self
CODE:
RETVAL = strPtr(storageFilterXsResultAll(ioReadFilterGroup(storageReadIo(self))));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
DESTROY(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageRead self
CODE:
storageReadFree(self);
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();

View File

@ -0,0 +1,4 @@
/***********************************************************************************************************************************
Storage Read XS Header
***********************************************************************************************************************************/
typedef StorageRead *pgBackRest__LibC__StorageRead;

View File

@ -0,0 +1,146 @@
# ----------------------------------------------------------------------------------------------------------------------------------
# Storage Write Exports
# ----------------------------------------------------------------------------------------------------------------------------------
MODULE = pgBackRest::LibC PACKAGE = pgBackRest::LibC::StorageWrite
####################################################################################################################################
pgBackRest::LibC::StorageWrite
new(class, storage, file, mode, user, group, timeModified, atomic, pathCreate)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
const String *class = STR_NEW_SV($arg);
pgBackRest::LibC::Storage storage
const String *file = STR_NEW_SV($arg);
U32 mode
const String *user = STR_NEW_SV($arg);
const String *group = STR_NEW_SV($arg);
IV timeModified
bool atomic
bool pathCreate
CODE:
CHECK(strEqZ(class, PACKAGE_NAME_LIBC "::StorageWrite"));
RETVAL = storageWriteMove(
storageNewWriteP(
storage, file, .modeFile = mode, .user = user, .group = group, .timeModified = (time_t)timeModified,
.noCreatePath = storageFeature(storage, storageFeaturePath) ? !pathCreate : false, .noSyncPath = !atomic,
.noAtomic = !atomic),
MEM_CONTEXT_XS_OLD());
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
filterAdd(self, filter, param)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageWrite self
const String *filter = STR_NEW_SV($arg);
const String *param = STR_NEW_SV($arg);
CODE:
IoFilterGroup *filterGroup = ioWriteFilterGroup(storageWriteIo(self));
storageFilterXsAdd(filterGroup, filter, param);
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
open(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageWrite self
CODE:
ioWriteOpen(storageWriteIo(self));
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
UV
write(self, buffer)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageWrite self
const Buffer *buffer = BUF_CONST_SV($arg);
CODE:
ioWrite(storageWriteIo(self), buffer);
RETVAL = bufUsed(buffer);
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
close(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageWrite self
CODE:
ioWriteClose(storageWriteIo(self));
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
const char *
result(self, filter)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageWrite self
const String *filter = STR_NEW_SV($arg);
CODE:
RETVAL = strPtr(storageFilterXsResult(ioWriteFilterGroup(storageWriteIo(self)), filter));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
const char *
resultAll(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageWrite self
CODE:
RETVAL = strPtr(storageFilterXsResultAll(ioWriteFilterGroup(storageWriteIo(self))));
OUTPUT:
RETVAL
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();
####################################################################################################################################
void
DESTROY(self)
PREINIT:
MEM_CONTEXT_XS_TEMP_BEGIN()
{
INPUT:
pgBackRest::LibC::StorageWrite self
CODE:
storageWriteFree(self);
CLEANUP:
}
MEM_CONTEXT_XS_TEMP_END();

View File

@ -0,0 +1,4 @@
/***********************************************************************************************************************************
Storage Write XS Header
***********************************************************************************************************************************/
typedef StorageWrite *pgBackRest__LibC__StorageWrite;

View File

@ -52,6 +52,7 @@ SRCS = \
command/archive/push/protocol.c \
command/archive/push/push.c \
command/backup/common.c \
command/backup/pageChecksum.c \
command/expire/expire.c \
command/help/help.c \
command/info/info.c \
@ -433,7 +434,7 @@ main.o: main.c build.auto.h command/archive/get/get.h command/archive/push/push.
perl/config.o: perl/config.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c perl/config.c -o perl/config.o
perl/exec.o: perl/exec.c ../libc/LibC.h build.auto.h common/assert.h common/crypto/cipherBlock.h common/crypto/common.h common/crypto/hash.h common/debug.h common/encode.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/filter.intern.h common/io/filter/group.h common/io/io.h common/io/read.h common/io/read.intern.h common/io/write.h common/io/write.intern.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h config/load.h config/parse.h perl/config.h perl/embed.auto.c perl/exec.h perl/libc.auto.c postgres/pageChecksum.h storage/info.h storage/posix/storage.h storage/read.h storage/read.intern.h storage/storage.h storage/storage.intern.h storage/write.h storage/write.intern.h version.h ../libc/xs/common/encode.xsh ../libc/xs/crypto/cipherBlock.xsh ../libc/xs/crypto/hash.xsh
perl/exec.o: perl/exec.c ../libc/LibC.h build.auto.h command/backup/pageChecksum.h common/assert.h common/compress/gzip/compress.h common/compress/gzip/decompress.h common/crypto/cipherBlock.h common/crypto/common.h common/crypto/hash.h common/debug.h common/encode.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/filter/size.h common/io/io.h common/io/read.h common/io/read.intern.h common/io/write.h common/io/write.intern.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h config/load.h config/parse.h perl/config.h perl/embed.auto.c perl/exec.h perl/libc.auto.c postgres/interface.h postgres/pageChecksum.h storage/helper.h storage/info.h storage/posix/storage.h storage/read.h storage/read.intern.h storage/storage.h storage/storage.intern.h storage/write.h storage/write.intern.h version.h ../libc/xs/common/encode.xsh ../libc/xs/crypto/hash.xsh ../libc/xs/storage/storage.xsh ../libc/xs/storage/storageRead.xsh ../libc/xs/storage/storageWrite.xsh
$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c perl/exec.c -o perl/exec.o
postgres/interface.o: postgres/interface.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/log.h common/logLevel.h common/memContext.h common/regExp.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h postgres/interface.h postgres/interface/version.h postgres/version.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h

View File

@ -90,7 +90,8 @@ restoreFile(
ioFilterGroupAdd(ioReadFilterGroup(read), cryptoHashNew(HASH_TYPE_SHA1_STR));
Buffer *buffer = bufNew(ioBufferSize());
ioReadOpen(read);
CHECK(ioReadOpen(read));
do
{

View File

@ -31,7 +31,7 @@ struct ErrorType
/***********************************************************************************************************************************
Maximum allowed number of nested try blocks
***********************************************************************************************************************************/
#define ERROR_TRY_MAX 32
#define ERROR_TRY_MAX 64
/***********************************************************************************************************************************
States for each try

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -28,7 +28,7 @@ struct Storage
mode_t modeFile;
mode_t modePath;
bool write;
bool pathResolve;
bool pathEnforce;
StoragePathExpressionCallback pathExpressionFunction;
};
@ -73,6 +73,7 @@ storageNew(
this->path = strDup(path);
this->modeFile = modeFile;
this->modePath = modePath;
this->pathEnforce = true;
this->write = write;
this->pathExpressionFunction = pathExpressionFunction;
@ -493,8 +494,8 @@ storagePath(const Storage *this, const String *pathExp)
// Make sure the base storage path is contained within the path expression
if (this->path != NULL && !strEqZ(this->path, "/"))
{
if (!strBeginsWith(pathExp, this->path) ||
!(strSize(pathExp) == strSize(this->path) || *(strPtr(pathExp) + strSize(this->path)) == '/'))
if (this->pathEnforce && (!strBeginsWith(pathExp, this->path) ||
!(strSize(pathExp) == strSize(this->path) || *(strPtr(pathExp) + strSize(this->path)) == '/')))
{
THROW_FMT(AssertError, "absolute path '%s' is not in base path '%s'", strPtr(pathExp), strPtr(this->path));
}
@ -586,10 +587,6 @@ storagePathCreate(const Storage *this, const String *pathExp, StoragePathCreateP
ASSERT(this->interface.pathCreate != NULL && storageFeature(this, storageFeaturePath));
ASSERT(this->write);
// It doesn't make sense to combine these parameters because if we are creating missing parent paths why error when they exist?
// If this somehow wasn't caught in testing, the worst case is that the path would not be created and an error would be thrown.
ASSERT(!(param.noParentCreate && param.errorOnExists));
MEM_CONTEXT_TEMP_BEGIN()
{
// Build the path
@ -781,6 +778,22 @@ storageInterface(const Storage *this)
FUNCTION_TEST_RETURN(this->interface);
}
/***********************************************************************************************************************************
Set whether absolute paths are required to be in the base path
***********************************************************************************************************************************/
void
storagePathEnforceSet(Storage *this, bool enforce)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STORAGE, this);
FUNCTION_TEST_PARAM(BOOL, enforce);
FUNCTION_TEST_END();
this->pathEnforce = enforce;
FUNCTION_TEST_RETURN_VOID();
}
/***********************************************************************************************************************************
Get the storage type (posix, cifs, etc.)
***********************************************************************************************************************************/

View File

@ -82,11 +82,15 @@ Storage *storageNew(
StoragePathExpressionCallback pathExpressionFunction, void *driver, StorageInterface interface);
/***********************************************************************************************************************************
Getters
Getters/Setters
***********************************************************************************************************************************/
void *storageDriver(const Storage *this);
StorageInterface storageInterface(const Storage *this);
// The option is intended to be used only with the Perl interface since Perl is not tidy about where it reads. It should be
// removed when the Perl interface is removed.
void storagePathEnforceSet(Storage *this, bool enforce);
/***********************************************************************************************************************************
Macros for function logging
***********************************************************************************************************************************/

View File

@ -276,13 +276,6 @@ unit:
- name: encode-perl
total: 1
# ----------------------------------------------------------------------------------------------------------------------------
- name: http-client-perl
total: 2
coverage:
Common/Http/Client: partial
# ----------------------------------------------------------------------------------------------------------------------------
- name: reg-exp
total: 3
@ -423,82 +416,18 @@ unit:
test:
# ----------------------------------------------------------------------------------------------------------------------------
- name: filter-cipher-block-perl
total: 2
- name: perl
total: 13
coverage:
Storage/Filter/CipherBlock: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: filter-gzip-perl
total: 3
coverage:
Storage/Filter/Gzip: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: filter-sha-perl
total: 2
coverage:
Storage/Filter/Sha: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: posix-perl
total: 10
coverage:
Storage/Posix/Driver: partial
Storage/Posix/FileRead: partial
Storage/Posix/FileWrite: partial
# ----------------------------------------------------------------------------------------------------------------------------
- name: s3-auth-perl
total: 5
coverage:
Storage/S3/Auth: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: s3-cert-perl
total: 1
# ----------------------------------------------------------------------------------------------------------------------------
- name: s3-request-perl
total: 2
coverage:
Storage/S3/Request: partial
# ----------------------------------------------------------------------------------------------------------------------------
- name: s3-perl
total: 7
coverage:
Storage/S3/Driver: partial
Storage/S3/FileRead: partial
Storage/S3/FileWrite: full
vm:
- co7
- u14
- u16
- u18
- d8
# ----------------------------------------------------------------------------------------------------------------------------
- name: local-perl
total: 10
coverage:
Storage/Local: partial
Storage/Storage: partial
# ----------------------------------------------------------------------------------------------------------------------------
- name: helper-perl
total: 4
total: 3
coverage:
Storage/Helper: partial
Storage/Helper: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: cifs
@ -840,7 +769,3 @@ performance:
# ----------------------------------------------------------------------------------------------------------------------------
- name: archive
total: 1
# ----------------------------------------------------------------------------------------------------------------------------
- name: io
total: 1

View File

@ -994,8 +994,8 @@ P00 WARN: option repo1-retention-full is not set, the repository may run out o
HINT: to retain full backups indefinitely (without warning), set option 'repo1-retention-full' to the maximum.
P00 INFO: last backup label = [BACKUP-FULL-2], version = [VERSION-1]
P00 WARN: incr backup cannot alter 'checksum-page' option to 'false', reset to 'true' from [BACKUP-FULL-2]
P00 ERROR: [055]: unable to stat '[TEST_PATH]/db-master/db/base_tbs': No such file or directory
P00 INFO: backup command end: aborted with exception [055]
P00 ERROR: [073]: unable to list file info for missing path '[TEST_PATH]/db-master/db/base_tbs'
P00 INFO: backup command end: aborted with exception [073]
incr backup - invalid tablespace in $PGDATA (db-master host)
> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --no-online --stanza=db backup

View File

@ -257,7 +257,7 @@ full backup - resume (backup host)
------------------------------------------------------------------------------------------------------------------------------------
P00 WARN: option repo1-retention-full is not set, the repository may run out of space
HINT: to retain full backups indefinitely (without warning), set option 'repo1-retention-full' to the maximum.
P00 WARN: backup [BACKUP-FULL-1] missing in repository removed from backup.info
P00 WARN: backup [BACKUP-FULL-1] missing manifest removed from backup.info
P00 WARN: --no-online passed and postmaster.pid exists but --force was passed so backup will continue though it looks like the postmaster is running and the backup will probably not be consistent
P00 WARN: aborted backup [BACKUP-FULL-2] of same type exists, will be cleaned to remove invalid files and resumed
P00 TEST: PgBaCkReStTeSt-BACKUP-RESUME-PgBaCkReStTeSt
@ -743,7 +743,7 @@ incr backup - resume and add tablespace 2 (backup host)
------------------------------------------------------------------------------------------------------------------------------------
P00 WARN: option repo1-retention-full is not set, the repository may run out of space
HINT: to retain full backups indefinitely (without warning), set option 'repo1-retention-full' to the maximum.
P00 WARN: backup [BACKUP-INCR-1] missing in repository removed from backup.info
P00 WARN: backup [BACKUP-INCR-1] missing manifest removed from backup.info
P00 WARN: incr backup cannot alter 'checksum-page' option to 'false', reset to 'true' from [BACKUP-FULL-2]
P00 WARN: file pg_data/changetime.txt timestamp in the past or size changed but timestamp did not, enabling delta checksum
P00 WARN: aborted backup [BACKUP-INCR-2] of same type exists, will be cleaned to remove invalid files and resumed
@ -3350,6 +3350,7 @@ P00 INFO: last backup label = [BACKUP-FULL-3], version = [VERSION-1]
P01 INFO: backup file db-master:[TEST_PATH]/db-master/db/base-2/base/base/base2.txt (9B, 100%) checksum cafac3c59553f2cfde41ce2e62e7662295f108c0
P00 INFO: diff backup size = 9B
P00 INFO: new backup label = [BACKUP-DIFF-6]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: backup command end: completed successfully
P00 INFO: expire command begin
P00 INFO: option 'repo1-retention-archive' is not set - archive logs will not be expired
@ -3558,6 +3559,7 @@ P00 INFO: last backup label = [BACKUP-FULL-3], version = [VERSION-1]
P01 INFO: backup file db-master:[TEST_PATH]/db-master/db/base-2/base/base/base2.txt (9B, 100%) checksum cafac3c59553f2cfde41ce2e62e7662295f108c0
P00 INFO: diff backup size = 9B
P00 INFO: new backup label = [BACKUP-DIFF-7]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: backup command end: completed successfully
P00 INFO: expire command begin
P00 INFO: option 'repo1-retention-archive' is not set - archive logs will not be expired

View File

@ -29,6 +29,8 @@ stanza-create db - stanza create (backup host)
> [CONTAINER-EXEC] backup [BACKREST-BIN] --config=[TEST_PATH]/backup/pgbackrest.conf --stanza=db --no-online --force stanza-create
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-create command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --force --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-2] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-create command end: completed successfully
+ supplemental file: /backup/db/backup.info

View File

@ -5,6 +5,8 @@ stanza-create db - create required data for stanza (backup host)
> [CONTAINER-EXEC] backup [BACKREST-BIN] --config=[TEST_PATH]/backup/pgbackrest.conf --stanza=db --no-online stanza-create
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-create command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-create command end: completed successfully
+ supplemental file: /backup/db/backup.info

View File

@ -209,7 +209,7 @@ stanza-create db - gunzip fail on forced stanza-create (db-master host)
> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --stanza=db --no-online --force stanza-create
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-create command begin [BACKREST-VERSION]: --compress-level=3 --config=[TEST_PATH]/db-master/pgbackrest.conf --db-timeout=45 --force --lock-path=[TEST_PATH]/db-master/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/db-master/log --log-subprocess --no-log-timestamp --no-online --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-path=[TEST_PATH]/db-master/repo --stanza=db
P00 ERROR: [041]: unable to open '[TEST_PATH]/db-master/repo/archive/db/9.3-1/0000000100000001/000000010000000100000001-488ba4b8b98acc510bce86b8f16e3c1ed9886a29.gz': Permission denied
P00 ERROR: [041]: unable to open file '[TEST_PATH]/db-master/repo/archive/db/9.3-1/0000000100000001/000000010000000100000001-488ba4b8b98acc510bce86b8f16e3c1ed9886a29.gz' for read: [13] Permission denied
P00 INFO: stanza-create command end: aborted with exception [041]
+ supplemental file: [TEST_PATH]/db-master/repo/backup/db/backup.info

View File

@ -16,12 +16,16 @@ P00 ERROR: [055]: archive.info does not exist but is required to push/get WAL s
HINT: is archive_command configured in postgresql.conf?
HINT: has a stanza-create been performed?
HINT: use --no-archive-check to disable archive checks during backup if you have an alternate archiving scheme.
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-upgrade command end: aborted with exception [055]
stanza-create db - successfully create the stanza (backup host)
> [CONTAINER-EXEC] backup [BACKREST-BIN] --config=[TEST_PATH]/backup/pgbackrest.conf --stanza=db --no-online stanza-create
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-create command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-create command end: completed successfully
+ supplemental file: /backup/db/backup.info
@ -67,6 +71,8 @@ stanza-create db - do not fail on rerun of stanza-create - info files exist and
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-create command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 INFO: stanza-create was already performed
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-create command end: completed successfully
+ supplemental file: /backup/db/backup.info
@ -113,6 +119,8 @@ stanza-create db - fail on database mismatch without force option (backup host)
P00 INFO: stanza-create command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 ERROR: [028]: backup info file or archive info file invalid
HINT: use stanza-upgrade if the database has been upgraded or use --force
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-create command end: aborted with exception [028]
+ supplemental file: /backup/db/backup.info
@ -158,6 +166,8 @@ stanza-upgrade db - already up to date (backup host)
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-upgrade command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 INFO: the stanza data is already up to date
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-upgrade command end: completed successfully
+ supplemental file: /backup/db/backup.info
@ -209,6 +219,8 @@ stanza-create db - force create archive.info from gz file (backup host)
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-create command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --force --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 ERROR: [055]: archive information missing and repo is encrypted and info file(s) are missing, --force cannot be used
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-create command end: aborted with exception [055]
+ supplemental file: /backup/db/backup.info
@ -246,6 +258,8 @@ stanza-upgrade db - successful upgrade creates additional history (backup host)
> [CONTAINER-EXEC] backup [BACKREST-BIN] --config=[TEST_PATH]/backup/pgbackrest.conf --stanza=db --no-online stanza-upgrade
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-upgrade command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-upgrade command end: completed successfully
+ supplemental file: /backup/db/backup.info
@ -309,6 +323,8 @@ P01 INFO: backup file db-master:[TEST_PATH]/db-master/db/base/pg_xlog/archive_
P01 INFO: backup file db-master:[TEST_PATH]/db-master/db/base/pg_xlog/archive_status/000000010000000100000001.ready (0B, 100%)
P00 INFO: full backup size = 48MB
P00 INFO: new backup label = [BACKUP-FULL-1]
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: backup command end: completed successfully
P00 INFO: expire command begin
P00 INFO: remove archive path: /archive/db/9.3-1
@ -381,6 +397,8 @@ stanza-create db - fail no force to recreate the stanza from backups (backup hos
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-create command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 ERROR: [055]: backup information missing and repo is encrypted and info file(s) are missing, --force cannot be used
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-create command end: aborted with exception [055]
+ supplemental file: /archive/db/archive.info
@ -407,6 +425,8 @@ stanza-create db - use force to recreate the stanza from backups (backup host)
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-create command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --force --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 ERROR: [055]: backup information missing and repo is encrypted and info file(s) are missing, --force cannot be used
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-create command end: aborted with exception [055]
+ supplemental file: /archive/db/archive.info
@ -432,6 +452,8 @@ stanza-upgrade db - successfully upgrade - no info file mismatch (backup host)
> [CONTAINER-EXEC] backup [BACKREST-BIN] --config=[TEST_PATH]/backup/pgbackrest.conf --stanza=db --no-online stanza-upgrade
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-upgrade command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --no-online --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-upgrade command end: completed successfully
+ supplemental file: /backup/db/backup.info
@ -495,6 +517,8 @@ P01 INFO: backup file db-master:[TEST_PATH]/db-master/db/base/pg_xlog/archive_
P01 INFO: backup file db-master:[TEST_PATH]/db-master/db/base/pg_xlog/archive_status/000000010000000100000001.ready (0B, 100%)
P00 INFO: full backup size = 48MB
P00 INFO: new backup label = [BACKUP-FULL-2]
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: backup command end: completed successfully
P00 INFO: expire command begin
P00 DETAIL: tls statistics:[TLS-STATISTICS]
@ -695,6 +719,8 @@ stanza-delete db - successfully delete the stanza (backup host)
> [CONTAINER-EXEC] backup [BACKREST-BIN] --config=[TEST_PATH]/backup/pgbackrest.conf --stanza=db stanza-delete
------------------------------------------------------------------------------------------------------------------------------------
P00 INFO: stanza-delete command begin [BACKREST-VERSION]: --compress-level=3 --compress-level-network=1 --config=[TEST_PATH]/backup/pgbackrest.conf --db-timeout=45 --lock-path=[TEST_PATH]/backup/lock --log-level-console=detail --log-level-file=trace --log-level-stderr=off --log-path=[TEST_PATH]/backup/log --log-subprocess --no-log-timestamp --pg1-host=db-master --pg1-host-cmd=[BACKREST-BIN] --pg1-host-config=[TEST_PATH]/db-master/pgbackrest.conf --pg1-host-user=[USER-1] --pg1-path=[TEST_PATH]/db-master/db/base --protocol-timeout=60 --repo1-cipher-pass=<redacted> --repo1-cipher-type=aes-256-cbc --repo1-path=/ --repo1-s3-bucket=pgbackrest-dev --repo1-s3-endpoint=s3.amazonaws.com --repo1-s3-key=<redacted> --repo1-s3-key-secret=<redacted> --repo1-s3-region=us-east-1 --no-repo1-s3-verify-tls --repo1-type=s3 --stanza=db
P00 DETAIL: tls statistics:[TLS-STATISTICS]
P00 INFO: http statistics:[HTTP-STATISTICS]
P00 INFO: stanza-delete command end: completed successfully
db must not exist for successful delete

View File

@ -136,7 +136,7 @@ sub testDefLoad
# Set module type variables
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_C} =
$strModuleType eq TESTDEF_UNIT && $strTest !~ /\-perl$/ ? true : false;
$strModuleType eq TESTDEF_UNIT && $strTest !~ /perl$/ ? true : false;
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_INTEGRATION} = $strModuleType eq TESTDEF_INTEGRATION ? true : false;
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_EXPECT} = $bExpect;
$hTestDefHash->{$strModule}{$strTest}{&TESTDEF_CONTAINER} = $bContainer;

View File

@ -27,8 +27,7 @@ use pgBackRest::Common::String;
use pgBackRest::Common::Wait;
use pgBackRest::Config::Config;
use pgBackRest::Manifest;
use pgBackRest::Storage::Local;
use pgBackRest::Storage::S3::Driver;
use pgBackRest::Storage::Base;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::HostGroupTest;
@ -168,7 +167,7 @@ sub forceStorageMode
);
# Mode commands are ignored on S3
if ($oStorage->driver()->className() ne STORAGE_S3_DRIVER)
if ($oStorage->type() ne STORAGE_S3)
{
executeTest('sudo chmod ' . ($bRecurse ? '-R ' : '') . "${strMode} " . $oStorage->pathGet($strPathExp));
}
@ -203,7 +202,7 @@ sub forceStorageMove
);
# If S3 then use storage commands to remove
if ($oStorage->driver()->className() eq STORAGE_S3_DRIVER)
if ($oStorage->type() eq STORAGE_S3)
{
hostGroupGet()->hostGet(HOST_S3)->executeS3(
'mv' . ($bRecurse ? ' --recursive' : '') . ' s3://' . HOST_S3_BUCKET . $oStorage->pathGet($strSourcePathExp) .
@ -244,8 +243,8 @@ sub forceStorageOwner
{name => 'bRecurse', optional => true, default => false},
);
# Mode commands are ignored on S3
if ($oStorage->driver()->className() ne STORAGE_S3_DRIVER)
# Owner commands are ignored on S3
if ($oStorage->type() ne STORAGE_S3)
{
executeTest('sudo chown ' . ($bRecurse ? '-R ' : '') . "${strOwner} " . $oStorage->pathGet($strPathExp));
}
@ -278,11 +277,19 @@ sub forceStorageRemove
);
# If S3 then use storage commands to remove
if ($oStorage->driver()->className() eq STORAGE_S3_DRIVER)
if ($oStorage->type() eq STORAGE_S3)
{
$oStorage->remove($strPathExp, {bRecurse => $bRecurse});
my $oInfo = $oStorage->info($strPathExp, {bIgnoreMissing => true});
if (defined($oInfo) && $oInfo->{type} eq 'f')
{
$oStorage->remove($strPathExp);
}
else
{
$oStorage->pathRemove($strPathExp, {bRecurse => true});
}
}
# Else remove using filesystem commands
else
{
executeTest('sudo rm -f' . ($bRecurse ? 'r ' : ' ') . $oStorage->pathGet($strPathExp));

View File

@ -19,10 +19,11 @@ use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Common::Wait;
use pgBackRest::Storage::Posix::Driver;
use pgBackRest::Storage::Local;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Storage;
use pgBackRest::Version;
use pgBackRestTest::Common::BuildTest;
use pgBackRestTest::Common::DefineTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::LogTest;
@ -155,7 +156,7 @@ sub process
$self->{bFirstTest} = true;
# Initialize test storage
$oStorage = new pgBackRest::Storage::Local($self->testPath(), new pgBackRest::Storage::Posix::Driver());
$oStorage = new pgBackRest::Storage::Storage(STORAGE_LOCAL, {strPath => $self->testPath()});
# Generate backrest exe
$self->{strBackRestExe} = testRunExe(

View File

@ -15,6 +15,7 @@ use Exporter qw(import);
our @EXPORT = qw();
use Fcntl ':mode';
use File::Basename qw(dirname);
use File::stat qw{lstat};
use Storable qw(dclone);
use pgBackRest::Archive::Info;
@ -27,9 +28,8 @@ use pgBackRest::Config::Config;
use pgBackRest::DbVersion;
use pgBackRest::Manifest;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Posix::Driver;
use pgBackRest::Storage::S3::Driver;
use pgBackRest::Version;
use pgBackRest::Storage::Base;
use pgBackRestTest::Env::Host::HostBaseTest;
use pgBackRestTest::Env::Host::HostS3Test;
@ -262,65 +262,68 @@ sub backupEnd
}
# Make sure tablespace links are correct
if (($strType eq CFGOPTVAL_BACKUP_TYPE_FULL || $self->hardLink()) && $self->hasLink())
if ($self->hasLink())
{
my $hTablespaceManifest = storageRepo()->manifest(
STORAGE_REPO_BACKUP . "/${strBackup}/" . MANIFEST_TARGET_PGDATA . '/' . DB_PATH_PGTBLSPC);
# Remove . and ..
delete($hTablespaceManifest->{'.'});
delete($hTablespaceManifest->{'..'});
# Iterate file links
for my $strFile (sort(keys(%{$hTablespaceManifest})))
if ($strType eq CFGOPTVAL_BACKUP_TYPE_FULL || $self->hardLink())
{
# Make sure the link is in the expected manifest
my $hManifestTarget =
$oExpectedManifest->{&MANIFEST_SECTION_BACKUP_TARGET}{&MANIFEST_TARGET_PGTBLSPC . "/${strFile}"};
my $hTablespaceManifest = storageRepo()->manifest(
STORAGE_REPO_BACKUP . "/${strBackup}/" . MANIFEST_TARGET_PGDATA . '/' . DB_PATH_PGTBLSPC);
if (!defined($hManifestTarget) || $hManifestTarget->{&MANIFEST_SUBKEY_TYPE} ne MANIFEST_VALUE_LINK ||
$hManifestTarget->{&MANIFEST_SUBKEY_TABLESPACE_ID} ne $strFile)
# Remove . and ..
delete($hTablespaceManifest->{'.'});
delete($hTablespaceManifest->{'..'});
# Iterate file links
for my $strFile (sort(keys(%{$hTablespaceManifest})))
{
confess &log(ERROR, "'${strFile}' is not in expected manifest as a link with the correct tablespace id");
# Make sure the link is in the expected manifest
my $hManifestTarget =
$oExpectedManifest->{&MANIFEST_SECTION_BACKUP_TARGET}{&MANIFEST_TARGET_PGTBLSPC . "/${strFile}"};
if (!defined($hManifestTarget) || $hManifestTarget->{&MANIFEST_SUBKEY_TYPE} ne MANIFEST_VALUE_LINK ||
$hManifestTarget->{&MANIFEST_SUBKEY_TABLESPACE_ID} ne $strFile)
{
confess &log(ERROR, "'${strFile}' is not in expected manifest as a link with the correct tablespace id");
}
# Make sure the link really is a link
if ($hTablespaceManifest->{$strFile}{type} ne 'l')
{
confess &log(ERROR, "'${strFile}' in tablespace directory is not a link");
}
# Make sure the link destination is correct
my $strLinkDestination = '../../' . MANIFEST_TARGET_PGTBLSPC . "/${strFile}";
if ($hTablespaceManifest->{$strFile}{link_destination} ne $strLinkDestination)
{
confess &log(ERROR,
"'${strFile}' link should reference '${strLinkDestination}' but actually references " .
"'$hTablespaceManifest->{$strFile}{link_destination}'");
}
}
# Make sure the link really is a link
if ($hTablespaceManifest->{$strFile}{type} ne 'l')
# Iterate manifest targets
for my $strTarget (sort(keys(%{$oExpectedManifest->{&MANIFEST_SECTION_BACKUP_TARGET}})))
{
confess &log(ERROR, "'${strFile}' in tablespace directory is not a link");
}
my $hManifestTarget = $oExpectedManifest->{&MANIFEST_SECTION_BACKUP_TARGET}{$strTarget};
my $strTablespaceId = $hManifestTarget->{&MANIFEST_SUBKEY_TABLESPACE_ID};
# Make sure the link destination is correct
my $strLinkDestination = '../../' . MANIFEST_TARGET_PGTBLSPC . "/${strFile}";
if ($hTablespaceManifest->{$strFile}{link_destination} ne $strLinkDestination)
{
confess &log(ERROR,
"'${strFile}' link should reference '${strLinkDestination}' but actually references " .
"'$hTablespaceManifest->{$strFile}{link_destination}'");
# Make sure the target exists as a link on disk
if ($hManifestTarget->{&MANIFEST_SUBKEY_TYPE} eq MANIFEST_VALUE_LINK && defined($strTablespaceId) &&
!defined($hTablespaceManifest->{$strTablespaceId}))
{
confess &log(ERROR,
"target '${strTarget}' does not have a link at '" . DB_PATH_PGTBLSPC. "/${strTablespaceId}'");
}
}
}
# Iterate manifest targets
for my $strTarget (sort(keys(%{$oExpectedManifest->{&MANIFEST_SECTION_BACKUP_TARGET}})))
# Else there should not be a tablespace directory at all
elsif (storageRepo()->pathExists(STORAGE_REPO_BACKUP . "/${strBackup}/" . MANIFEST_TARGET_PGDATA . '/' . DB_PATH_PGTBLSPC))
{
my $hManifestTarget = $oExpectedManifest->{&MANIFEST_SECTION_BACKUP_TARGET}{$strTarget};
my $strTablespaceId = $hManifestTarget->{&MANIFEST_SUBKEY_TABLESPACE_ID};
# Make sure the target exists as a link on disk
if ($hManifestTarget->{&MANIFEST_SUBKEY_TYPE} eq MANIFEST_VALUE_LINK && defined($strTablespaceId) &&
!defined($hTablespaceManifest->{$strTablespaceId}))
{
confess &log(ERROR,
"target '${strTarget}' does not have a link at '" . DB_PATH_PGTBLSPC. "/${strTablespaceId}'");
}
confess &log(ERROR, 'backup must be full or hard-linked to have ' . DB_PATH_PGTBLSPC . ' directory');
}
}
# Else there should not be a tablespace directory at all
elsif (storageRepo()->pathExists(STORAGE_REPO_BACKUP . "/${strBackup}/" . MANIFEST_TARGET_PGDATA . '/' . DB_PATH_PGTBLSPC))
{
confess &log(ERROR, 'backup must be full or hard-linked to have ' . DB_PATH_PGTBLSPC . ' directory');
}
# Check that latest link exists unless repo links are disabled
my $strLatestLink = storageRepo()->pathGet(STORAGE_REPO_BACKUP . qw{/} . LINK_LATEST);
@ -495,7 +498,8 @@ sub backupCompare
my $lRepoSize =
$oActualManifest->test(MANIFEST_SECTION_TARGET_FILE, $strFileKey, MANIFEST_SUBKEY_REFERENCE) ?
$oActualManifest->numericGet(MANIFEST_SECTION_TARGET_FILE, $strFileKey, MANIFEST_SUBKEY_REPO_SIZE, false) :
(storageRepo()->info(STORAGE_REPO_BACKUP . "/${strBackup}/${strFileKey}" . ($bCompressed ? '.gz' : '')))->size;
(storageRepo()->info(STORAGE_REPO_BACKUP .
"/${strBackup}/${strFileKey}" . ($bCompressed ? '.gz' : '')))->{size};
if (defined($lRepoSize) &&
$lRepoSize != $oExpectedManifest->{&MANIFEST_SECTION_TARGET_FILE}{$strFileKey}{&MANIFEST_SUBKEY_SIZE})
@ -1882,7 +1886,7 @@ sub restoreCompare
if ($oActualManifest->get(MANIFEST_SECTION_TARGET_FILE, $strName, MANIFEST_SUBKEY_SIZE) != 0)
{
my $oStat = storageTest()->info($oActualManifest->dbPathGet($strSectionPath, $strName));
my $oStat = lstat($oActualManifest->dbPathGet($strSectionPath, $strName));
# When performing a selective restore, the files for the database(s) that are not restored are still copied but as empty
# sparse files (blocks == 0). If the file is not a sparse file or is a link, then get the actual checksum for comparison
@ -2049,8 +2053,8 @@ sub backupDestination {return shift->{strBackupDestination}}
sub backrestExe {return testRunGet()->backrestExe()}
sub bogusHost {return shift->{bBogusHost}}
sub hardLink {return shift->{bHardLink}}
sub hasLink {storageRepo()->driver()->className() eq STORAGE_POSIX_DRIVER}
sub isFS {storageRepo()->driver()->className() ne STORAGE_S3_DRIVER}
sub hasLink {storageRepo()->capability(STORAGE_CAPABILITY_LINK)}
sub isFS {storageRepo()->type() ne STORAGE_S3}
sub isHostBackup {my $self = shift; return $self->backupDestination() eq $self->nameGet()}
sub isHostDbMaster {return shift->nameGet() eq HOST_DB_MASTER}
sub isHostDbStandby {return shift->nameGet() eq HOST_DB_STANDBY}

View File

@ -224,7 +224,7 @@ sub manifestFileCreate
my $strPathFile = $self->dbFileCreate($oManifestRef, $strTarget, $strFile, $strContent, $lTime, $strMode);
# Stat the file
my $oStat = storageTest()->info($strPathFile);
my $oStat = lstat($strPathFile);
${$oManifestRef}{&MANIFEST_SECTION_TARGET_FILE}{$strManifestKey}{&MANIFEST_SUBKEY_GROUP} = getgrgid($oStat->gid);
${$oManifestRef}{&MANIFEST_SECTION_TARGET_FILE}{$strManifestKey}{&MANIFEST_SUBKEY_USER} = getpwuid($oStat->uid);
@ -338,7 +338,7 @@ sub manifestLinkCreate
my $strDbFile = $self->dbLinkCreate($oManifestRef, $strPath, $strFile, $strDestination);
# Stat the link
my $oStat = storageTest()->info($strDbFile);
my $oStat = lstat($strDbFile);
# Check for errors in stat
if (!defined($oStat))
@ -360,7 +360,7 @@ sub manifestLinkCreate
(defined(dirname($strPath)) ? dirname($strPath) : '') . "/${strDestination}";
}
$oStat = storageTest()->info($strDestinationFile);
$oStat = lstat($strDestinationFile);
my $strSection = MANIFEST_SECTION_TARGET_PATH;
@ -556,7 +556,7 @@ sub manifestTablespaceCreate
# Load linked path into manifest
my $strLinkPath = $self->tablespacePath($iOid);
my $strTarget = MANIFEST_TARGET_PGTBLSPC . "/${iOid}";
my $oStat = storageTest()->info($strLinkPath);
my $oStat = lstat($strLinkPath);
${$oManifestRef}{&MANIFEST_SECTION_TARGET_PATH}{$strTarget}{&MANIFEST_SUBKEY_GROUP} = getgrgid($oStat->gid);
${$oManifestRef}{&MANIFEST_SECTION_TARGET_PATH}{$strTarget}{&MANIFEST_SUBKEY_USER} = getpwuid($oStat->uid);
@ -582,7 +582,7 @@ sub manifestTablespaceCreate
}
# Load tablespace path into manifest
$oStat = storageTest()->info($strTablespacePath);
$oStat = lstat($strTablespacePath);
${$oManifestRef}{&MANIFEST_SECTION_TARGET_PATH}{&MANIFEST_TARGET_PGTBLSPC} =
${$oManifestRef}{&MANIFEST_SECTION_TARGET_PATH}{&MANIFEST_TARGET_PGDATA};
@ -599,7 +599,7 @@ sub manifestTablespaceCreate
or confess "unable to link ${strLink} to ${strLinkPath}";
# Load link into the manifest
$oStat = storageTest()->info($strLink);
$oStat = lstat($strLink);
my $strLinkTarget = MANIFEST_TARGET_PGDATA . "/${strTarget}";
${$oManifestRef}{&MANIFEST_SECTION_TARGET_LINK}{$strLinkTarget}{&MANIFEST_SUBKEY_GROUP} = getgrgid($oStat->gid);

View File

@ -163,10 +163,9 @@ sub run
MANIFEST_SUBKEY_CHECKSUM, $strPgControlHash)}, true, "manifest updated for pg_control");
# Neither backup.manifest nor backup.manifest.copy written because size threshold not met
$self->testException(sub {storageRepo()->openRead("$strBackupPath/" . FILE_MANIFEST . INI_COPY_EXT)}, ERROR_FILE_MISSING,
"unable to open '$strBackupPath/" . FILE_MANIFEST . INI_COPY_EXT . "': No such file or directory");
$self->testException(sub {storageRepo()->openRead("$strBackupPath/" . FILE_MANIFEST)}, ERROR_FILE_MISSING,
"unable to open '$strBackupPath/" . FILE_MANIFEST . "': No such file or directory");
$self->testResult(sub {storageRepo()->exists("$strBackupPath/" . FILE_MANIFEST)}, false, "backup.manifest missing");
$self->testResult(
sub {storageRepo()->exists("$strBackupPath/" . FILE_MANIFEST . INI_COPY_EXT)}, false, "backup.manifest.copy missing");
#---------------------------------------------------------------------------------------------------------------------------
# No prior checksum, no compression, no page checksum, no extra, no delta, no hasReference
@ -180,8 +179,7 @@ sub run
$lResultCopySize == $lFileSize && $lResultRepoSize == $lFileSize), true,
'file copied to repo successfully');
$self->testException(sub {storageRepo()->openRead("$strFileRepo.gz")}, ERROR_FILE_MISSING,
"unable to open '$strFileRepo.gz': No such file or directory");
$self->testResult(sub {storageRepo()->exists("${strFileRepo}.gz")}, false, "${strFileRepo}.gz missing");
($lSizeCurrent, $lManifestSaveCurrent) = backupManifestUpdate(
$oBackupManifest,
@ -212,18 +210,13 @@ sub run
# Backup.manifest not written but backup.manifest.copy written because size threshold met
$self->testResult(sub {storageTest()->exists("$strBackupPath/" . FILE_MANIFEST . INI_COPY_EXT)}, true,
'backup.manifest.copy exists in repo');
$self->testException(sub {storageRepo()->openRead("$strBackupPath/" . FILE_MANIFEST)}, ERROR_FILE_MISSING,
"unable to open '$strBackupPath/" . FILE_MANIFEST . "': No such file or directory");
$self->testResult(
sub {storageRepo()->exists("$strBackupPath/" . FILE_MANIFEST)}, false, 'backup.manifest.copy missing in repo');
storageTest()->remove($strFileRepo);
storageTest()->remove($strPgControlRepo);
#---------------------------------------------------------------------------------------------------------------------------
# No prior checksum, yes compression, yes page checksum, no extra, no delta, no hasReference
$self->testException(sub {backupFile($strFileDb, $strRepoFile, $lFileSize, undef, true,
$strBackupLabel, true, cfgOption(CFGOPT_COMPRESS_LEVEL), $lFileTime, true, undef, false, false, undef)}, ERROR_ASSERT,
"iWalId is required in Backup::Filter::PageChecksum->new");
# Build the lsn start parameter to pass to the extra function
my $hStartLsnParam =
{
@ -231,7 +224,6 @@ sub run
iWalOffset => 0xFFFF,
};
#---------------------------------------------------------------------------------------------------------------------------
# No prior checksum, yes compression, yes page checksum, yes extra, no delta, no hasReference
($iResultCopyResult, $lResultCopySize, $lResultRepoSize, $strResultCopyChecksum, $rResultExtra) =
backupFile($strFileDb, $strRepoFile, $lFileSize, undef, true, $strBackupLabel, true,
@ -243,8 +235,7 @@ sub run
$lResultRepoSize == $lRepoFileCompressSize && $rResultExtra->{bValid}), true, 'file copied to repo successfully');
# Only the compressed version of the file exists
$self->testException(sub {storageRepo()->openRead("$strFileRepo")}, ERROR_FILE_MISSING,
"unable to open '$strFileRepo': No such file or directory");
$self->testResult(sub {storageRepo()->exists("$strFileRepo")}, false, "only compressed version exists");
($lSizeCurrent, $lManifestSaveCurrent) = backupManifestUpdate(
$oBackupManifest,
@ -368,7 +359,7 @@ sub run
# do not ignoreMissing
$self->testException(sub {backupFile("$strFileDb.1", "$strRepoFile.1", $lFileSize, $strFileHash,
false, $strBackupLabel, false, cfgOption(CFGOPT_COMPRESS_LEVEL), $lFileTime, false, undef, true, false, undef)},
ERROR_FILE_MISSING, "unable to open '$strFileDb.1': No such file or directory");
ERROR_FILE_MISSING, "unable to open missing file '${strFileDb}.1' for read");
#---------------------------------------------------------------------------------------------------------------------------
# Restore the compressed file
@ -518,7 +509,7 @@ sub run
$self->testException(sub {backupFile($strFileDb, $strRepoFile, $lFileSize, $strFileHash,
false, $strBackupLabel, false, cfgOption(CFGOPT_COMPRESS_LEVEL), $lFileTime, true, undef, true, false, undef)},
ERROR_FILE_MISSING, "unable to open '$strFileRepo': No such file or directory");
ERROR_FILE_MISSING, "unable to open missing file '${strFileRepo}' for read");
}
################################################################################################################################

View File

@ -1,173 +0,0 @@
####################################################################################################################################
# S3 Request Tests
####################################################################################################################################
package pgBackRestTest::Module::Common::CommonHttpClientPerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use IO::Socket::SSL;
use POSIX qw(strftime);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Http::Client;
use pgBackRest::Common::Log;
use pgBackRest::Common::Wait;
use pgBackRestTest::Common::ContainerTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# Port to use for testing
####################################################################################################################################
use constant HTTPS_TEST_PORT => 9443;
####################################################################################################################################
# httpsServerResponse
####################################################################################################################################
sub httpsServerResponse
{
my $self = shift;
my $iResponseCode = shift;
my $strContent = shift;
# Write header
$self->{oConnection}->write("HTTP/1.1 ${iResponseCode} GenericMessage\r\n");
$self->{oConnection}->write(HTTP_HEADER_CONTENT_LENGTH . ': ' . (defined($strContent) ? length($strContent) : 0) . "\r\n");
# Write new line before content (even if there isn't any)
$self->{oConnection}->write("\r\n");
# Write content
if (defined($strContent))
{
$self->{oConnection}->write($strContent);
}
# This will block until the connection is closed by the client
$self->{oConnection}->read();
}
####################################################################################################################################
# httpsServerAccept
####################################################################################################################################
sub httpsServerAccept
{
my $self = shift;
# Wait for a connection
$self->{oConnection} = $self->{oSocketServer}->accept()
or confess "failed to accept or handshake $!, $SSL_ERROR";
&log(INFO, " * socket server connected");
}
####################################################################################################################################
# httpsServer
####################################################################################################################################
sub httpsServer
{
my $self = shift;
my $fnServer = shift;
# Fork off the server
if (fork() == 0)
{
# Run server function
$fnServer->();
exit 0;
}
}
####################################################################################################################################
# Start the https testing server
####################################################################################################################################
sub initModule
{
my $self = shift;
# Open the domain socket
$self->{oSocketServer} = IO::Socket::SSL->new(
LocalAddr => '127.0.0.1', LocalPort => HTTPS_TEST_PORT, Listen => 1, SSL_cert_file => CERT_FAKE_SERVER,
SSL_key_file => CERT_FAKE_SERVER_KEY)
or confess "unable to open https server for testing: $!";
&log(INFO, " * socket server open");
}
####################################################################################################################################
# Stop the https testing server
####################################################################################################################################
sub cleanModule
{
my $self = shift;
# Shutdown server
$self->{oSocketServer}->close();
&log(INFO, " * socket server closed");
}
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Test variables
my $strTestHost = '127.0.0.1';
my $strTestData = 'TESTDATA';
################################################################################################################################
if ($self->begin('content-length defined'))
{
$self->httpsServer(sub
{
$self->httpsServerAccept();
$self->httpsServerResponse(200, $strTestData);
});
#---------------------------------------------------------------------------------------------------------------------------
my $oHttpClient = $self->testResult(
sub {new pgBackRest::Common::Http::Client(
$strTestHost, HTTP_VERB_GET, {iPort => HTTPS_TEST_PORT, bVerifySsl => false})},
'[object]', 'new http client');
$self->testResult(sub {${$oHttpClient->responseBody()}}, $strTestData, 'response body read');
}
################################################################################################################################
if ($self->begin('retry'))
{
$self->httpsServer(sub
{
$self->httpsServerAccept();
$self->{oConnection}->write("HTTP/1.1 404 Error\r\nBogus-Header\r\n\r\n");
$self->httpsServerAccept();
$self->{oConnection}->write("HTTP/1.1 404 Error\r\nBogus-Header\r\n\r\n");
$self->httpsServerAccept();
$self->httpsServerResponse(200, $strTestData);
});
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {new pgBackRest::Common::Http::Client(
$strTestHost, HTTP_VERB_GET, {iPort => HTTPS_TEST_PORT, bVerifySsl => false, iTryTotal => 1})},
ERROR_PROTOCOL, "http header 'Bogus-Header' requires colon separator");
$self->testResult(
sub {new pgBackRest::Common::Http::Client(
$strTestHost, HTTP_VERB_GET, {iPort => HTTPS_TEST_PORT, bVerifySsl => false, iTryTotal => 2})},
'[object]', 'successful retries');
}
}
1;

View File

@ -297,42 +297,6 @@ sub run
$self->testException(sub {new pgBackRest::Common::Ini($strTestFile)}, ERROR_CRYPTO,
"unable to parse '$strTestFile'" .
"\nHINT: Is or was the repo encrypted?");
# Encryption
#---------------------------------------------------------------------------------------------------------------------------
executeTest("rm -rf ${strTestFile}*");
my $strCipherPass = 'x';
my $strCipherPassSub = 'y';
# Unencrypted storage but a passphrase passed
$self->testException(sub {new pgBackRest::Common::Ini($strTestFile, {bLoad => false,
strCipherPass => $strCipherPass})}, ERROR_ASSERT,
"a user passphrase and sub passphrase are both required when encrypting");
# Unencrypted storage but a sub passphrase passed
$self->testException(sub {new pgBackRest::Common::Ini($strTestFile, {bLoad => false,
strCipherPassSub => $strCipherPassSub})}, ERROR_ASSERT,
"a user passphrase and sub passphrase are both required when encrypting");
# Create Encrypted storage
my $oStorage = new pgBackRest::Storage::Local($self->testPath(), new pgBackRest::Storage::Posix::Driver(),
{strCipherType => CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC, strCipherPassUser => $strCipherPass});
$self->testException(sub {new pgBackRest::Common::Ini($strTestFile, {oStorage => $oStorage})}, ERROR_CRYPTO,
"passphrase is required when storage is encrypted");
$self->testException(sub {new pgBackRest::Common::Ini($strTestFile, {bLoad => false, oStorage => $oStorage,
strCipherPass => $strCipherPass})}, ERROR_ASSERT,
"a user passphrase and sub passphrase are both required when encrypting");
$oIni = $self->testResult(sub {
new pgBackRest::Common::Ini(
$strTestFile,
{bLoad => false, oStorage => $oStorage, strCipherPass => $strCipherPass, strCipherPassSub => $strCipherPassSub})},
'[object]', 'create new ini with encryption passphrases');
$self->testResult(sub {($oIni->cipherPassSub() eq $strCipherPassSub) &&
($oIni->cipherPass() eq $strCipherPass)}, true, ' new ini has encryption passphrases');
}
################################################################################################################################

View File

@ -198,8 +198,8 @@ sub run
# Attempt to save without proper path
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(sub {$oManifest->save()}, ERROR_PATH_MISSING,
"unable to open '" . $strBackupManifestFile . "': No such file or directory");
$self->testException(sub {$oManifest->save()}, ERROR_FILE_MISSING,
"unable to open file '${strBackupManifestFile}' for write in missing path");
# Create path and save
#---------------------------------------------------------------------------------------------------------------------------
@ -226,8 +226,8 @@ sub run
# Build error if offline = true and no tablespace path
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, false, false)}, ERROR_FILE_MISSING,
"unable to stat '" . $self->{strDbPath} . "/" . MANIFEST_TARGET_PGTBLSPC . "': No such file or directory");
$self->testException(sub {$oManifest->build(storageDb(), $self->{strDbPath}, undef, false, false)}, ERROR_PATH_MISSING,
"unable to list file info for missing path '" . $self->{strDbPath} . "/" . MANIFEST_TARGET_PGTBLSPC . "'");
# bOnline = true tests
#---------------------------------------------------------------------------------------------------------------------------
@ -236,7 +236,11 @@ sub run
{bLoad => false, strDbVersion => PG_VERSION_94, iDbCatalogVersion => $self->dbCatalogVersion(PG_VERSION_94)});
# Create expected manifest from base
my $oStorageTemp = $oManifestBase->{oStorage};
$oManifestBase->{oStorage} = undef;
my $oManifestExpected = dclone($oManifestBase);
$oManifestBase->{oStorage} = $oStorageTemp;
$oManifestExpected->{oStorage} = $oStorageTemp;
# Add global/pg_control file and PG_VERSION file and create a directory with a different modes than default
storageDb()->put(storageDb()->openWrite($self->{strDbPath} . '/' . DB_FILE_PGCONTROL,
@ -523,7 +527,11 @@ sub run
# Unskip code path coverage
#---------------------------------------------------------------------------------------------------------------------------
$oStorageTemp = $oManifestExpected->{oStorage};
$oManifestExpected->{oStorage} = undef;
my $oManifestExpectedUnskip = dclone($oManifestExpected);
$oManifestExpected->{oStorage} = $oStorageTemp;
$oManifestExpectedUnskip->{oStorage} = $oStorageTemp;
# Change DB version to 93
$oManifest = new pgBackRest::Manifest(
@ -954,7 +962,7 @@ sub run
storageDb()->remove('postgresql.auto.conf');
storageDb()->remove('hosts');
storageDb()->remove('pg_log/logfile');
storageDb()->remove('global/exclude', {bRecurse => true});
storageDb()->pathRemove('global/exclude', {bRecurse => true});
# Reload the manifest with version < 9.0
#---------------------------------------------------------------------------------------------------------------------------
@ -1229,7 +1237,11 @@ sub run
{bLoad => false, strDbVersion => PG_VERSION_94, iDbCatalogVersion => $self->dbCatalogVersion(PG_VERSION_94)});
# Create expected manifest from base
my $oStorageTemp = $oManifestBase->{oStorage};
$oManifestBase->{oStorage} = undef;
my $oManifestExpected = dclone($oManifestBase);
$oManifestBase->{oStorage} = $oStorageTemp;
$oManifestExpected->{oStorage} = $oStorageTemp;
# Future timestamp on file
#---------------------------------------------------------------------------------------------------------------------------
@ -1255,7 +1267,11 @@ sub run
# Future timestamp in last manifest
#---------------------------------------------------------------------------------------------------------------------------
$oStorageTemp = $oManifestExpected->{oStorage};
$oManifestExpected->{oStorage} = undef;
my $oLastManifest = dclone($oManifestExpected);
$oManifestExpected->{oStorage} = $oStorageTemp;
$oLastManifest->{oStorage} = $oStorageTemp;
# Set a backup label
$oLastManifest->set(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL, undef, BOGUS);
@ -1507,7 +1523,12 @@ sub run
{
my $oManifest = new pgBackRest::Manifest($strBackupManifestFile, {bLoad => false, strDbVersion => PG_VERSION_94,
iDbCatalogVersion => $self->dbCatalogVersion(PG_VERSION_94)});
my $oStorageTemp = $oManifestBase->{oStorage};
$oManifestBase->{oStorage} = undef;
my $oManifestExpected = dclone($oManifestBase);
$oManifestBase->{oStorage} = $oStorageTemp;
$oManifestExpected->{oStorage} = $oStorageTemp;
# Add a bogus file - all traces to be removed after the manifest has been built to simulate an inital manifest and avoid
# missing files error

View File

@ -878,7 +878,7 @@ sub run
$oHostBackup->backup(
$strType, '$PGDATA is a substring of valid tblspc excluding / (file missing err expected)',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_FILE_MISSING});
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_PATH_MISSING});
testFileRemove("${strTblSpcPath}/99999");
}

View File

@ -73,14 +73,12 @@ sub run
true, $self->expect(), {bHostBackup => $bRemote, bCompress => $bCompress, bArchiveAsync => true, bS3 => $bS3,
bRepoEncrypt => $bEncrypt});
my $oStorage = storageRepo();
# Create compression extension
my $strCompressExt = $bCompress ? qw{.} . COMPRESS_EXT : '';
# Create the wal path
my $strWalPath = $oHostDbMaster->dbBasePath() . '/pg_xlog';
$oStorage->pathCreate($strWalPath, {bCreateParent => true});
storageTest()->pathCreate($strWalPath, {bCreateParent => true});
# Create the test path for pg_control and generate pg_control for stanza-create
storageTest()->pathCreate($oHostDbMaster->dbBasePath() . '/' . DB_PATH_GLOBAL, {bCreateParent => true});
@ -97,7 +95,7 @@ sub run
if ($iError == 0)
{
$oHostBackup->infoMunge(
$oStorage->pathGet(STORAGE_REPO_ARCHIVE . qw{/} . ARCHIVE_INFO_FILE),
storageRepo()->pathGet(STORAGE_REPO_ARCHIVE . qw{/} . ARCHIVE_INFO_FILE),
{&INFO_ARCHIVE_SECTION_DB => {&INFO_ARCHIVE_KEY_DB_VERSION => '8.0'},
&INFO_ARCHIVE_SECTION_DB_HISTORY => {1 => {&INFO_ARCHIVE_KEY_DB_VERSION => '8.0'}}});
}
@ -121,12 +119,12 @@ sub run
# Fix the database version
if ($iError == 0)
{
$oHostBackup->infoRestore($oStorage->pathGet(STORAGE_REPO_ARCHIVE . qw{/} . ARCHIVE_INFO_FILE));
$oHostBackup->infoRestore(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE . qw{/} . ARCHIVE_INFO_FILE));
}
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$oStorage->list(
sub {storageRepo()->list(
STORAGE_REPO_ARCHIVE . qw{/} . PG_VERSION_94 . '-1/0000000100000001')},
"000000010000000100000001-${strWalHash}${strCompressExt}",
'segment 2-4 not pushed', {iWaitSeconds => 5});
@ -135,7 +133,7 @@ sub run
$oHostDbMaster->archivePush($strWalPath, $strWalTestFile, 5);
$self->testResult(
sub {$oStorage->list(
sub {storageRepo()->list(
STORAGE_REPO_ARCHIVE . qw{/} . PG_VERSION_94 . '-1/0000000100000001')},
"(000000010000000100000001-${strWalHash}${strCompressExt}, " .
"000000010000000100000005-${strWalHash}${strCompressExt})",

View File

@ -25,7 +25,6 @@ use pgBackRest::InfoCommon;
use pgBackRest::Manifest;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::Gzip;
use pgBackRest::Storage::Helper;
use pgBackRestTest::Env::HostEnvTest;
@ -198,7 +197,7 @@ sub run
storageRepo()->copy(
storageRepo()->openRead(
STORAGE_REPO_ARCHIVE . "/${strArchiveTest}.gz",
{rhyFilter => [{strClass => STORAGE_FILTER_GZIP, rxyParam => [{strCompressType => STORAGE_DECOMPRESS}]}]}),
{rhyFilter => [{strClass => STORAGE_FILTER_GZIP, rxyParam => [STORAGE_DECOMPRESS, false]}]}),
STORAGE_REPO_ARCHIVE . "/${strArchiveTest}");
$oHostBackup->stanzaCreate('force create archive.info from uncompressed file',

View File

@ -1,127 +0,0 @@
####################################################################################################################################
# I/O Performance Tests
####################################################################################################################################
package pgBackRestTest::Module::Performance::PerformanceIoTest;
use parent 'pgBackRestTest::Env::ConfigEnvTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Storable qw(dclone);
use Time::HiRes qw(gettimeofday);
use pgBackRest::Common::Log;
use pgBackRest::Config::Config;
use pgBackRest::Protocol::Helper;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Filter::Gzip;
use pgBackRest::Storage::Filter::Sha;
use pgBackRest::Storage::Helper;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# initModule
####################################################################################################################################
sub initModule
{
my $self = shift;
# Load reference page data
my $tPageBin = ${storageTest()->get($self->dataPath() . '/filecopy.table.bin')};
# Create large test file
$self->{iTableLargeSize} = 32;
$self->{strTableLargeFile} = 'table-large.bin';
my $oFileWrite = storageTest()->openWrite($self->{strTableLargeFile});
for (my $iIndex = 0; $iIndex < $self->{iTableLargeSize}; $iIndex++)
{
$oFileWrite->write(\$tPageBin);
}
$oFileWrite->close();
}
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
################################################################################################################################
if ($self->begin("copy"))
{
# Setup the remote for testing remote storage
$self->optionTestSet(CFGOPT_STANZA, $self->stanza());
$self->optionTestSet(CFGOPT_PG_PATH, $self->testPath());
$self->optionTestSet(CFGOPT_REPO_PATH, $self->testPath());
$self->optionTestSet(CFGOPT_LOG_PATH, $self->testPath());
$self->optionTestSet(CFGOPT_REPO_HOST, 'localhost');
$self->optionTestSet(CFGOPT_REPO_HOST_USER, $self->backrestUser());
$self->configTestLoad(CFGCMD_RESTORE);
protocolGet(CFGOPTVAL_REMOTE_TYPE_BACKUP, undef, {strBackRestBin => $self->backrestExe()});
storageRepo();
# Setup file info
my $strFile = $self->{strTableLargeFile};
my $strFileCopy = "${strFile}.copy";
my $iRunTotal = 1;
&log(INFO, "time is average of ${iRunTotal} run(s)");
foreach my $bGzip (false, true)
{
foreach my $bRemote (false, true)
{
my $rhyFilter;
push(@{$rhyFilter}, {strClass => STORAGE_FILTER_SHA});
push(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP, rxyParam => [{iLevel => 6}]}) if ($bGzip);
my $lTimeTotal = 0;
my $lTimeBegin;
for (my $iIndex = 0; $iIndex < $iRunTotal; $iIndex++)
{
# Get the remote or local for writing
my $oStorageWrite = $bRemote ? storageRepo() : storageTest();
# Start the timer
$lTimeBegin = gettimeofday();
# Copy the file
storageTest()->copy(
storageTest()->openRead($strFile, {rhyFilter => $rhyFilter}),
$oStorageWrite->openWrite($strFileCopy));
# Record time
$lTimeTotal += gettimeofday() - $lTimeBegin;
# Remove file so it can be copied again
executeTest("sudo rm " . $oStorageWrite->pathGet($strFileCopy));
}
# Calculate out output metrics
my $fExecutionTime = int($lTimeTotal * 1000 / $iRunTotal) / 1000;
my $fGbPerHour = int((60 * 60) * 1000 / ((1024 / $self->{iTableLargeSize}) * $fExecutionTime)) / 1000;
&log(INFO, "sha1 1, gz ${bGzip}, rmt ${bRemote}: ${fExecutionTime}s, ${fGbPerHour} GB/hr");
}
}
# Destroy protocol object
protocolDestroy();
}
}
1;

View File

@ -36,6 +36,8 @@ use pgBackRestTest::Env::Host::HostBaseTest;
use pgBackRestTest::Env::Host::HostBackupTest;
use pgBackRestTest::Env::Host::HostDbTest;
use pgBackRestTest::Env::HostEnvTest;
use pgBackRestTest::Common::Storage;
use pgBackRestTest::Common::StoragePosix;
####################################################################################################################################
# run
@ -270,7 +272,7 @@ sub run
executeTest("sudo chmod 400 ${strDir}");
$strComment = 'confirm master manifest->build executed';
$oHostDbMaster->check($strComment, {iTimeout => 5, iExpectedExitStatus => ERROR_FILE_OPEN});
$oHostDbMaster->check($strComment, {iTimeout => 5, iExpectedExitStatus => ERROR_PATH_OPEN});
executeTest("sudo rmdir ${strDir}");
# Providing a sufficient archive-timeout, verify that the check command runs successfully now with valid
@ -503,10 +505,10 @@ sub run
my $strComment = 'confirm standby manifest->build executed';
# If there is an invalid host, the final error returned from check will be the inability to resolve the name which is
# a read error instead of an open error
# an open error instead of a read error
if (!$oHostDbStandby->bogusHost())
{
$oHostDbStandby->check($strComment, {iTimeout => 5, iExpectedExitStatus => ERROR_FILE_OPEN});
$oHostDbStandby->check($strComment, {iTimeout => 5, iExpectedExitStatus => ERROR_PATH_OPEN});
}
else
{
@ -746,13 +748,19 @@ sub run
{
my ($strSHA1, $lSize) = storageTest()->hashSize($strDb1TablePath);
# Create a zeroed sparse file in the test directory that is the same size as the filenode.map
# Create a zeroed sparse file in the test directory that is the same size as the filenode.map. We need to use the
# posix driver directly to do this because handles cannot be passed back from the C code.
my $oStorageTrunc = new pgBackRestTest::Common::Storage($self->testPath(), new pgBackRestTest::Common::StoragePosix());
my $strTestTable = $self->testPath() . "/testtable";
my $oDestinationFileIo = storageTest()->openWrite($strTestTable);
my $oDestinationFileIo = $oStorageTrunc->openWrite($strTestTable);
$oDestinationFileIo->open();
# Truncate to the original size which will create a sparse file.
truncate($oDestinationFileIo->handle(), $lSize);
if (!truncate($oDestinationFileIo->handle(), $lSize))
{
confess "unable to truncate '$strTestTable' with handle " . $oDestinationFileIo->handle();
}
$oDestinationFileIo->close();
# Confirm the test filenode.map and the database test1 filenode.map are zeroed

View File

@ -280,7 +280,7 @@ sub run
# Change the permissions on the archived file so reconstruction fails
executeTest('sudo chmod 220 ' . $strArchivedFile);
$self->testException(sub {(new pgBackRest::Stanza())->stanzaCreate()}, ERROR_FILE_OPEN,
"unable to open '" . $strArchivedFile . "': Permission denied");
"unable to open file '${strArchivedFile}' for read");
executeTest('sudo chmod 644 ' . $strArchivedFile);
# Clear the cached repo settings and change repo settings to encrypted
@ -312,7 +312,10 @@ sub run
#---------------------------------------------------------------------------------------------------------------------------
storageRepo()->pathCreate(STORAGE_REPO_ARCHIVE . "/" . PG_VERSION_94 . "-1");
storageRepo()->pathCreate(STORAGE_REPO_ARCHIVE . "/" . PG_VERSION_94 . "-1/0000000100000001");
storageRepo()->put($strArchivedFile, $tUnencryptedArchiveContent);
storageRepo()->put(
storageRepo()->openWrite(
$strArchivedFile, {strCipherPass => new pgBackRest::Archive::Info($self->{strArchivePath})->cipherPassSub()}),
$tUnencryptedArchiveContent);
storageRepo()->pathCreate($strBackupPath); # Empty backup path - no backup in progress
# Confirm encrypted and create the stanza with force
@ -477,7 +480,7 @@ sub run
#---------------------------------------------------------------------------------------------------------------------------
executeTest('sudo chmod 220 ' . $self->{strArchivePath});
$self->testException(sub {$oStanza->infoObject(STORAGE_REPO_ARCHIVE, $self->{strArchivePath})}, ERROR_FILE_OPEN,
"unable to open '" . $self->{strArchivePath} . "/archive.info': Permission denied");
"unable to open file '" . $self->{strArchivePath} . "/archive.info' for read");
executeTest('sudo chmod 640 ' . $self->{strArchivePath});
# Reset force option --------
@ -492,8 +495,8 @@ sub run
forceStorageRemove(storageRepo(), storageRepo()->pathGet(STORAGE_REPO_BACKUP . qw{/} . FILE_BACKUP_INFO . INI_COPY_EXT));
executeTest('sudo chmod 220 ' . storageRepo()->pathGet(STORAGE_REPO_BACKUP . qw{/} . FILE_BACKUP_INFO));
$self->testException(sub {$oStanza->infoObject(STORAGE_REPO_BACKUP, $self->{strBackupPath})}, ERROR_FILE_OPEN,
"unable to open '" . storageRepo()->pathGet(STORAGE_REPO_BACKUP . qw{/} . FILE_BACKUP_INFO) .
"': Permission denied");
"unable to open file '" . storageRepo()->pathGet(STORAGE_REPO_BACKUP . qw{/} . FILE_BACKUP_INFO) .
"' for read");
executeTest('sudo chmod 640 ' . storageRepo()->pathGet(STORAGE_REPO_BACKUP . qw{/} . FILE_BACKUP_INFO));
}
@ -762,8 +765,8 @@ sub run
executeTest("sudo chown 7777 " . $self->{strArchivePath});
lockStop();
$self->testException(sub {$oStanza->stanzaDelete()}, ERROR_FILE_OPEN,
"unable to remove file '" . $self->{strArchivePath} . "/" . ARCHIVE_INFO_FILE . "': Permission denied");
$self->testException(sub {$oStanza->stanzaDelete()}, ERROR_FILE_REMOVE,
"unable to remove '" . $self->{strArchivePath} . "/" . ARCHIVE_INFO_FILE . "'");
# Remove the repo
executeTest("sudo rm -rf " . $self->{strArchivePath});

View File

@ -1,285 +0,0 @@
####################################################################################################################################
# Tests for Block Cipher
####################################################################################################################################
package pgBackRestTest::Module::Storage::StorageFilterCipherBlockPerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Fcntl qw(O_RDONLY);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::LibC qw(:random :crypto);
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::CipherBlock;
use pgBackRest::Storage::Posix::Driver;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Test data
my $strFile = $self->testPath() . qw{/} . 'file.txt';
my $strFileEncrypt = $self->testPath() . qw{/} . 'file.enc.txt';
my $strFileDecrypt = $self->testPath() . qw{/} . 'file.dcr.txt';
my $strFileBin = $self->testPath() . qw{/} . 'file.bin';
my $strFileBinEncrypt = $self->testPath() . qw{/} . 'file.enc.bin';
my $strFileContent = 'TESTDATA';
my $iFileLength = length($strFileContent);
my $oDriver = new pgBackRest::Storage::Posix::Driver();
my $tCipherPass = 'areallybadkey';
my $strCipherType = 'aes-256-cbc';
my $tContent;
################################################################################################################################
if ($self->begin('new()'))
{
#---------------------------------------------------------------------------------------------------------------------------
# Create an unencrypted file
executeTest("echo -n '${strFileContent}' | tee ${strFile}");
$self->testException(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openRead($strFile), $strCipherType, $tCipherPass, {strMode => BOGUS})},
ERROR_ASSERT, 'unknown cipher mode: ' . BOGUS);
$self->testException(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openRead($strFile), BOGUS, $tCipherPass)},
ERROR_ASSERT, "invalid cipher name '" . BOGUS . "'");
$self->testException(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openWrite($strFile), $strCipherType, $tCipherPass, {strMode => BOGUS})},
ERROR_ASSERT, 'unknown cipher mode: ' . BOGUS);
$self->testException(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openWrite($strFile), BOGUS, $tCipherPass)},
ERROR_ASSERT, "invalid cipher name '" . BOGUS . "'");
}
################################################################################################################################
if ($self->begin('read() and write()'))
{
my $tBuffer;
#---------------------------------------------------------------------------------------------------------------------------
# Create an plaintext file
executeTest("echo -n '${strFileContent}' | tee ${strFile}");
# Instantiate the cipher object - default action encrypt
my $oEncryptIo = $self->testResult(sub {new pgBackRest::Storage::Filter::CipherBlock($oDriver->openRead($strFile),
$strCipherType, $tCipherPass)}, '[object]', 'new encrypt file');
$self->testResult(sub {$oEncryptIo->read(\$tBuffer, 2)}, 16, ' read 16 bytes (header)');
$self->testResult(sub {$oEncryptIo->read(\$tBuffer, 2)}, 16, ' read 16 bytes (data)');
$self->testResult(sub {$oEncryptIo->read(\$tBuffer, 2)}, 0, ' read 0 bytes');
$self->testResult(sub {$tBuffer ne $strFileContent}, true, ' data read is encrypted');
$self->testResult(sub {$oEncryptIo->close()}, true, ' close');
$self->testResult(sub {$oEncryptIo->close()}, false, ' close again');
# tBuffer is now encrypted - test write decrypts correctly
my $oDecryptFileIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock($oDriver->openWrite($strFileDecrypt),
$strCipherType, $tCipherPass, {strMode => STORAGE_DECRYPT})},
'[object]', ' new decrypt file');
$self->testResult(sub {$oDecryptFileIo->write(\$tBuffer)}, 32, ' write decrypted');
$self->testResult(sub {$oDecryptFileIo->close()}, true, ' close');
$self->testResult(sub {${$self->storageTest()->get($strFileDecrypt)}}, $strFileContent, ' data written is decrypted');
#---------------------------------------------------------------------------------------------------------------------------
$tBuffer = $strFileContent;
my $oEncryptFileIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock($oDriver->openWrite($strFileEncrypt),
$strCipherType, $tCipherPass)},
'[object]', 'new write encrypt');
$tContent = '';
$self->testResult(sub {$oEncryptFileIo->write(\$tContent)}, 0, ' attempt empty buffer write');
undef($tContent);
$self->testException(
sub {$oEncryptFileIo->write(\$tContent)}, ERROR_FILE_WRITE,
"unable to write to '${strFileEncrypt}': Use of uninitialized value");
# Encrypted length is not known so use tBuffer then test that tBuffer was encrypted
my $iWritten = $self->testResult(sub {$oEncryptFileIo->write(\$tBuffer)}, length($tBuffer), ' write encrypted');
$self->testResult(sub {$oEncryptFileIo->close()}, true, ' close');
$tContent = $self->storageTest()->get($strFileDecrypt);
$self->testResult(sub {defined($tContent) && $tContent ne $strFileContent}, true, ' data written is encrypted');
#---------------------------------------------------------------------------------------------------------------------------
undef($tBuffer);
# Open encrypted file for decrypting
$oEncryptFileIo =
$self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openRead($strFileEncrypt), $strCipherType, $tCipherPass,
{strMode => STORAGE_DECRYPT})},
'[object]', 'new read encrypted file, decrypt');
# Try to read more than the length of the data expected to be output from the decrypt and confirm the decrypted length is
# the same as the original decrypted content.
$self->testResult(sub {$oEncryptFileIo->read(\$tBuffer, $iFileLength+4)}, $iFileLength, ' read all bytes');
# Just because length is the same does not mean content is so confirm
$self->testResult($tBuffer, $strFileContent, ' data read is decrypted');
$self->testResult(sub {$oEncryptFileIo->close()}, true, ' close');
#---------------------------------------------------------------------------------------------------------------------------
undef($tContent);
undef($tBuffer);
my $strFileBinHash = '1c7e00fd09b9dd11fc2966590b3e3274645dd031';
executeTest('cp ' . $self->dataPath() . "/filecopy.archive2.bin ${strFileBin}");
$self->testResult(
sub {cryptoHashOne('sha1', ${storageTest()->get($strFileBin)})}, $strFileBinHash, 'bin test - check sha1');
$tContent = ${storageTest()->get($strFileBin)};
$oEncryptFileIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openWrite($strFileBinEncrypt), $strCipherType, $tCipherPass)},
'[object]', ' new write encrypt');
$self->testResult(sub {$oEncryptFileIo->write(\$tContent)}, length($tContent), ' write encrypted');
$self->testResult(sub {$oEncryptFileIo->close()}, true, ' close');
$self->testResult(
sub {cryptoHashOne('sha1', ${storageTest()->get($strFileBinEncrypt)}) ne $strFileBinHash}, true,
' check sha1 different');
my $oEncryptBinFileIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openRead($strFileBinEncrypt), $strCipherType, $tCipherPass,
{strMode => STORAGE_DECRYPT})},
'[object]', 'new read encrypted bin file');
$self->testResult(sub {$oEncryptBinFileIo->read(\$tBuffer, 16777216)}, 16777216, ' read 16777216 bytes');
$self->testResult(sub {cryptoHashOne('sha1', $tBuffer)}, $strFileBinHash, ' check sha1 same as original');
$self->testResult(sub {$oEncryptBinFileIo->close()}, true, ' close');
# Try to read the file with the wrong passphrase
undef($tBuffer);
undef($oEncryptBinFileIo);
$oEncryptBinFileIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openRead($strFileBinEncrypt), $strCipherType, BOGUS,
{strMode => STORAGE_DECRYPT})},
'[object]', 'new read Encrypted bin file with wrong passphrase');
$self->testResult(sub {$oEncryptBinFileIo->read(\$tBuffer, 16777216)}, 16777216, ' read all bytes');
$self->testResult(sub {cryptoHashOne('sha1', $tBuffer) ne $strFileBinHash}, true, ' check sha1 NOT same as original');
# Test file against openssl to make sure they are compatible
#---------------------------------------------------------------------------------------------------------------------------
undef($tBuffer);
$self->storageTest()->put($strFile, $strFileContent);
executeTest(
"openssl enc -k ${tCipherPass} -md sha1 -aes-256-cbc -in ${strFile} -out ${strFileEncrypt}");
$oEncryptFileIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openRead($strFileEncrypt), $strCipherType, $tCipherPass,
{strMode => STORAGE_DECRYPT})},
'[object]', 'read file encrypted by openssl');
$self->testResult(sub {$oEncryptFileIo->read(\$tBuffer, 16)}, 8, ' read 8 bytes');
$self->testResult(sub {$oEncryptFileIo->close()}, true, ' close');
$self->testResult(sub {$tBuffer}, $strFileContent, ' check content same as original');
$self->storageTest()->remove($strFile);
$self->storageTest()->remove($strFileEncrypt);
$oEncryptFileIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openWrite($strFileEncrypt), $strCipherType, $tCipherPass)},
'[object]', 'write file to be read by openssl');
$self->testResult(sub {$oEncryptFileIo->write(\$tBuffer)}, 8, ' write 8 bytes');
$self->testResult(sub {$oEncryptFileIo->close()}, true, ' close');
executeTest(
"openssl enc -d -k ${tCipherPass} -md sha1 -aes-256-cbc -in ${strFileEncrypt} -out ${strFile}");
$self->testResult(sub {${$self->storageTest()->get($strFile)}}, $strFileContent, ' check content same as original');
# Test empty file against openssl to make sure they are compatible
#---------------------------------------------------------------------------------------------------------------------------
$tBuffer = '';
$self->storageTest()->put($strFile);
executeTest(
"openssl enc -k ${tCipherPass} -md sha1 -aes-256-cbc -in ${strFile} -out ${strFileEncrypt}");
$oEncryptFileIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openRead($strFileEncrypt), $strCipherType, $tCipherPass,
{strMode => STORAGE_DECRYPT})},
'[object]', 'read empty file encrypted by openssl');
$self->testResult(sub {$oEncryptFileIo->read(\$tBuffer, 16)}, 0, ' read 0 bytes');
$self->testResult(sub {$oEncryptFileIo->close()}, true, ' close');
$self->testResult(sub {$tBuffer}, '', ' check content same as original');
$self->storageTest()->remove($strFile);
$self->storageTest()->remove($strFileEncrypt);
$oEncryptFileIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openWrite($strFileEncrypt), $strCipherType, $tCipherPass)},
'[object]', 'write file to be read by openssl');
$self->testResult(sub {$oEncryptFileIo->write(\$tBuffer)}, 0, ' write 0 bytes');
$self->testResult(sub {$oEncryptFileIo->close()}, true, ' close');
executeTest(
"openssl enc -d -k ${tCipherPass} -md sha1 -aes-256-cbc -in ${strFileEncrypt} -out ${strFile}");
$self->testResult(sub {${$self->storageTest()->get($strFile)}}, undef, ' check content same as original');
# Error on empty file decrypt - an empty file that has been encrypted will be 32 bytes
#---------------------------------------------------------------------------------------------------------------------------
undef($tBuffer);
$self->storageTest()->put($strFileEncrypt);
$oEncryptFileIo =
$self->testResult(
sub {new pgBackRest::Storage::Filter::CipherBlock(
$oDriver->openRead($strFileEncrypt), $strCipherType, $tCipherPass,
{strMode => STORAGE_DECRYPT})},
'[object]', 'new read empty attempt decrypt');
$self->testException(sub {$oEncryptFileIo->read(\$tBuffer, 16)}, ERROR_CRYPTO, 'cipher header missing');
$self->testResult(sub {$oEncryptFileIo->close()}, true, 'close');
# OpenSSL should error on the empty file
executeTest(
"openssl enc -d -k ${tCipherPass} -md sha1 -aes-256-cbc -in ${strFileEncrypt} -out ${strFile}",
{iExpectedExitStatus => 1});
}
}
1;

View File

@ -1,222 +0,0 @@
####################################################################################################################################
# Tests for Storage::Filter::Gzip module
####################################################################################################################################
package pgBackRestTest::Module::Storage::StorageFilterGzipPerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Compress::Raw::Zlib qw(Z_OK Z_BUF_ERROR Z_DATA_ERROR);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::LibC qw(:crypto);
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::Gzip;
use pgBackRest::Storage::Posix::Driver;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Test data
my $strFile = $self->testPath() . qw{/} . 'file.txt';
my $strFileGz = "${strFile}.gz";
my $strFileContent = 'TESTDATA';
my $iFileLength = length($strFileContent);
my $oDriver = new pgBackRest::Storage::Posix::Driver();
################################################################################################################################
if ($self->begin('errorCheck()'))
{
#---------------------------------------------------------------------------------------------------------------------------
my $oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip($oDriver->openWrite($strFileGz))}, '[object]', 'new write');
$oGzipIo->{bWrite} = true;
$self->testException(sub {$oGzipIo->errorCheck(Z_DATA_ERROR)}, ERROR_FILE_WRITE, "unable to deflate '${strFileGz}'");
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oGzipIo->errorCheck(Z_OK)}, Z_OK, 'Z_OK');
$self->testResult(sub {$oGzipIo->errorCheck(Z_BUF_ERROR)}, Z_OK, 'Z_BUF_ERROR');
#---------------------------------------------------------------------------------------------------------------------------
$oGzipIo->{bWrite} = false;
$oGzipIo->{strCompressType} = STORAGE_DECOMPRESS;
$self->testException(sub {$oGzipIo->errorCheck(Z_DATA_ERROR)}, ERROR_FILE_READ, "unable to inflate '${strFileGz}'");
}
################################################################################################################################
if ($self->begin('write()'))
{
#---------------------------------------------------------------------------------------------------------------------------
my $oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip($oDriver->openWrite($strFileGz), {lCompressBufferMax => 4})},
'[object]', 'new write compress');
my $tBuffer = substr($strFileContent, 0, 2);
$self->testResult(sub {$oGzipIo->write(\$tBuffer)}, 2, ' write 2 bytes');
$tBuffer = substr($strFileContent, 2, 2);
$self->testResult(sub {$oGzipIo->write(\$tBuffer)}, 2, ' write 2 bytes');
$tBuffer = substr($strFileContent, 4, 2);
$self->testResult(sub {$oGzipIo->write(\$tBuffer)}, 2, ' write 2 bytes');
$tBuffer = substr($strFileContent, 6, 2);
$self->testResult(sub {$oGzipIo->write(\$tBuffer)}, 2, ' write 2 bytes');
$tBuffer = '';
$self->testResult(sub {$oGzipIo->write(\$tBuffer)}, 0, ' write 0 bytes');
$self->testResult(sub {$oGzipIo->close()}, true, ' close');
executeTest("gzip -d ${strFileGz}");
$self->testResult(sub {${storageTest()->get($strFile)}}, $strFileContent, ' check content');
#---------------------------------------------------------------------------------------------------------------------------
executeTest("gzip ${strFile}");
my $tFile = ${storageTest()->get($strFileGz)};
$oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip(
$oDriver->openWrite($strFile), {strCompressType => STORAGE_DECOMPRESS})}, '[object]', 'new write decompress');
$tBuffer = substr($tFile, 0, 10);
$self->testResult(sub {$oGzipIo->write(\$tBuffer)}, 10, ' write bytes');
$tBuffer = substr($tFile, 10);
$self->testResult(sub {$oGzipIo->write(\$tBuffer)}, length($tFile) - 10, ' write bytes');
$self->testResult(sub {$oGzipIo->close()}, true, ' close');
$self->testResult(sub {${storageTest()->get($strFile)}}, $strFileContent, ' check content');
}
################################################################################################################################
if ($self->begin('read()'))
{
my $tBuffer;
#---------------------------------------------------------------------------------------------------------------------------
my $oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip($oDriver->openWrite($strFileGz), {bWantGzip => false, iLevel => 3})},
'[object]', 'new write compress');
$self->testResult($oGzipIo->{iLevel}, 3, ' check level');
$self->testResult(sub {$oGzipIo->write(\$strFileContent, $iFileLength)}, $iFileLength, ' write');
$self->testResult(sub {$oGzipIo->close()}, true, ' close');
#---------------------------------------------------------------------------------------------------------------------------
$oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip(
$oDriver->openRead($strFileGz), {bWantGzip => false, strCompressType => STORAGE_DECOMPRESS})},
'[object]', 'new read decompress');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 4)}, 4, ' read 4 bytes');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2)}, 2, ' read 2 bytes');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2)}, 2, ' read 2 bytes');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2)}, 0, ' read 0 bytes');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2)}, 0, ' read 0 bytes');
$self->testResult(sub {$oGzipIo->close()}, true, ' close');
$self->testResult(sub {$oGzipIo->close()}, false, ' close again');
$self->testResult($tBuffer, $strFileContent, ' check content');
storageTest()->remove($strFileGz);
#---------------------------------------------------------------------------------------------------------------------------
$tBuffer = 'AA';
storageTest()->put($strFile, $strFileContent);
$oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip($oDriver->openRead($strFile))},
'[object]', 'new read compress');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2)}, 10, ' read 10 bytes (request 2)');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2)}, 18, ' read 18 bytes (request 2)');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2)}, 0, ' read 0 bytes (request 2)');
$self->testResult(sub {$oGzipIo->close()}, true, ' close');
$self->testResult(sub {storageTest()->put($strFileGz, substr($tBuffer, 2))}, 28, ' put content');
executeTest("gzip -df ${strFileGz}");
$self->testResult(sub {${storageTest()->get($strFile)}}, $strFileContent, ' check content');
#---------------------------------------------------------------------------------------------------------------------------
$tBuffer = undef;
executeTest('cat ' . $self->dataPath() . "/filecopy.archive2.bin | gzip -c > ${strFileGz}");
$oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip(
$oDriver->openRead($strFileGz), {lCompressBufferMax => 4096, strCompressType => STORAGE_DECOMPRESS})},
'[object]', 'new read decompress');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 8388608)}, 8388608, ' read 8388608 bytes');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 4194304)}, 4194304, ' read 4194304 bytes');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 4194304)}, 4194304, ' read 4194304 bytes');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 1)}, 0, ' read 0 bytes');
$self->testResult(sub {$oGzipIo->close()}, true, ' close');
$self->testResult(cryptoHashOne('sha1', $tBuffer), '1c7e00fd09b9dd11fc2966590b3e3274645dd031', ' check content');
storageTest()->remove($strFileGz);
#---------------------------------------------------------------------------------------------------------------------------
$tBuffer = undef;
executeTest('cp ' . $self->dataPath() . "/filecopy.archive2.bin ${strFile}");
$oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip($oDriver->openRead($strFile), {strCompressType => STORAGE_COMPRESS})},
'[object]', 'new read compress');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2000000) > 0}, true, ' read bytes');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2000000) > 0}, true, ' read bytes');
$self->testResult(sub {$oGzipIo->close()}, true, ' close');
$self->testResult(sub {storageTest()->put($strFileGz, $tBuffer) > 0}, true, ' put content');
executeTest("gzip -df ${strFileGz}");
$self->testResult(
sub {cryptoHashOne('sha1', ${storageTest()->get($strFile)})}, '1c7e00fd09b9dd11fc2966590b3e3274645dd031',
' check content');
#---------------------------------------------------------------------------------------------------------------------------
$tBuffer = undef;
my $oFile = $self->testResult(
sub {storageTest()->openWrite($strFile)}, '[object]', 'open file to extend during compression');
$oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip($oDriver->openRead($strFile), {lCompressBufferMax => 4194304})},
'[object]', ' new read compress');
$self->testResult(sub {$oFile->write(\$strFileContent)}, length($strFileContent), ' write first block');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2000000) > 0}, true, ' read compressed first block (compression done)');
$self->testResult(sub {$oFile->write(\$strFileContent)}, length($strFileContent), ' write second block');
$self->testResult(sub {$oGzipIo->read(\$tBuffer, 2000000)}, 0, ' read compressed = 0');
$self->testResult(sub {storageTest()->put($strFileGz, $tBuffer) > 0}, true, ' put content');
executeTest("gzip -df ${strFileGz}");
$self->testResult(
sub {${storageTest()->get($strFile)}}, $strFileContent, ' check content');
#---------------------------------------------------------------------------------------------------------------------------
storageTest()->put($strFileGz, $strFileContent);
$oGzipIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Gzip($oDriver->openRead($strFileGz), {strCompressType => STORAGE_DECOMPRESS})},
'[object]', 'new read decompress');
$self->testException(
sub {$oGzipIo->read(\$tBuffer, 1)}, ERROR_FILE_READ, "unable to inflate '${strFileGz}': incorrect header check");
}
}
1;

View File

@ -1,107 +0,0 @@
####################################################################################################################################
# Tests for StorageFilterSha module
####################################################################################################################################
package pgBackRestTest::Module::Storage::StorageFilterShaPerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::LibC qw(:crypto);
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Filter::Sha;
use pgBackRest::Storage::Posix::Driver;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Test data
my $strFile = $self->testPath() . qw{/} . 'file.txt';
my $strFileContent = 'TESTDATA';
my $iFileLength = length($strFileContent);
my $oDriver = new pgBackRest::Storage::Posix::Driver();
################################################################################################################################
if ($self->begin('read()'))
{
my $tBuffer;
#---------------------------------------------------------------------------------------------------------------------------
executeTest("echo -n '${strFileContent}' | tee ${strFile}");
my $oFileIo = $self->testResult(sub {$oDriver->openRead($strFile)}, '[object]', 'open read');
my $oShaIo = $self->testResult(sub {new pgBackRest::Storage::Filter::Sha($oFileIo)}, '[object]', 'new read');
$self->testResult(sub {$oShaIo->read(\$tBuffer, 2, undef)}, 2, 'read 2 bytes');
$self->testResult(sub {$oShaIo->read(\$tBuffer, 2, 2)}, 2, 'read 2 bytes');
$self->testResult(sub {$oShaIo->read(\$tBuffer, 2, 4)}, 2, 'read 2 bytes');
$self->testResult(sub {$oShaIo->read(\$tBuffer, 2, 6)}, 2, 'read 2 bytes');
$self->testResult(sub {$oShaIo->read(\$tBuffer, 2, 8)}, 0, 'read 0 bytes');
$self->testResult(sub {$oShaIo->close()}, true, 'close');
my $strSha = $self->testResult(
sub {$oShaIo->result(STORAGE_FILTER_SHA)}, cryptoHashOne('sha1', $strFileContent),
'check hash against original content');
$self->testResult($strSha, cryptoHashOne('sha1', $tBuffer), 'check hash against buffer');
$self->testResult(sub {${storageTest()->get($strFile)}}, $strFileContent, 'check content');
#---------------------------------------------------------------------------------------------------------------------------
$tBuffer = undef;
$oFileIo = $self->testResult(
sub {$oDriver->openRead($self->dataPath() . '/filecopy.archive2.bin')}, '[object]', 'open read');
$oShaIo = $self->testResult(sub {new pgBackRest::Storage::Filter::Sha($oFileIo)}, '[object]', 'new read');
$self->testResult(sub {$oShaIo->read(\$tBuffer, 8388608)}, 8388608, ' read 8388608 bytes');
$self->testResult(sub {$oShaIo->read(\$tBuffer, 4194304)}, 4194304, ' read 4194304 bytes');
$self->testResult(sub {$oShaIo->read(\$tBuffer, 4194304)}, 4194304, ' read 4194304 bytes');
$self->testResult(sub {$oShaIo->read(\$tBuffer, 1)}, 0, ' read 0 bytes');
$self->testResult(sub {$oShaIo->close()}, true, ' close');
$self->testResult(sub {$oShaIo->close()}, false, ' close again to make sure nothing bad happens');
$self->testResult($oShaIo->result(STORAGE_FILTER_SHA), '1c7e00fd09b9dd11fc2966590b3e3274645dd031', ' check hash');
$self->testResult(cryptoHashOne('sha1', $tBuffer), '1c7e00fd09b9dd11fc2966590b3e3274645dd031', ' check content');
}
################################################################################################################################
if ($self->begin('write()'))
{
#---------------------------------------------------------------------------------------------------------------------------
my $oFileIo = $self->testResult(sub {$oDriver->openWrite($strFile, {bAtomic => true})}, '[object]', 'open write');
my $oShaIo = $self->testResult(
sub {new pgBackRest::Storage::Filter::Sha($oFileIo)}, '[object]', 'new');
my $tBuffer = substr($strFileContent, 0, 2);
$self->testResult(sub {$oShaIo->write(\$tBuffer)}, 2, 'write 2 bytes');
$tBuffer = substr($strFileContent, 2, 2);
$self->testResult(sub {$oShaIo->write(\$tBuffer)}, 2, 'write 2 bytes');
$tBuffer = substr($strFileContent, 4, 2);
$self->testResult(sub {$oShaIo->write(\$tBuffer)}, 2, 'write 2 bytes');
$tBuffer = substr($strFileContent, 6, 2);
$self->testResult(sub {$oShaIo->write(\$tBuffer)}, 2, 'write 2 bytes');
$tBuffer = '';
$self->testResult(sub {$oShaIo->write(\$tBuffer)}, 0, 'write 0 bytes');
$self->testResult(sub {$oShaIo->close()}, true, 'close');
my $strSha = $self->testResult(
sub {$oShaIo->result(STORAGE_FILTER_SHA)}, cryptoHashOne('sha1', $strFileContent),
'check hash against original content');
$self->testResult(sub {${storageTest()->get($strFile)}}, $strFileContent, 'check content');
}
}
1;

View File

@ -61,11 +61,12 @@ sub run
#-------------------------------------------------------------------------------------------------------------------------------
if ($self->begin("storageLocal()"))
{
$self->testResult(sub {storageLocal($self->testPath())->put($strFile, $strFileContent)}, $iFileSize, 'put');
$self->testResult(sub {${storageTest()->get($strFile)}}, $strFileContent, ' check put');
$self->testResult(sub {storageLocal($self->testPath())->put("/tmp/${strFile}", $strFileContent)}, $iFileSize, 'put');
$self->testResult(sub {${storageTest()->get("/tmp/${strFile}")}}, $strFileContent, ' check put');
$self->testResult(sub {storageLocal($self->testPath())->put($strFile, $strFileContent)}, $iFileSize, 'put cache storage');
$self->testResult(sub {${storageTest()->get($strFile)}}, $strFileContent, ' check put');
$self->testResult(
sub {storageLocal($self->testPath())->put("/tmp/${strFile}", $strFileContent)}, $iFileSize, 'put cache storage');
$self->testResult(sub {${storageTest()->get("/tmp/${strFile}")}}, $strFileContent, ' check put');
}
#-------------------------------------------------------------------------------------------------------------------------------
@ -109,44 +110,6 @@ sub run
$self->testResult(
sub {storageRepo()->pathGet(STORAGE_REPO_BACKUP . '/file')}, $self->testPath() . '/repo/backup/db/file',
'check backup file');
#---------------------------------------------------------------------------------------------------------------------------
# Insert a bogus rule to generate an error
storageRepo()->{hRule}{'<BOGUS>'} =
{
fnRule => storageRepo()->{hRule}{&STORAGE_REPO_ARCHIVE}{fnRule},
};
$self->testException(sub {storageRepo()->pathGet('<BOGUS>')}, ERROR_ASSERT, 'invalid <REPO> storage rule <BOGUS>');
}
#-------------------------------------------------------------------------------------------------------------------------------
if ($self->begin("storageRepo() encryption"))
{
my $strStanzaEncrypt = 'test-encrypt';
$self->optionTestSet(CFGOPT_REPO_CIPHER_TYPE, CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC);
$self->testException(
sub {$self->configTestLoad(CFGCMD_ARCHIVE_PUSH)}, ERROR_OPTION_REQUIRED,
'archive-push command requires option: repo1-cipher-pass');
# Set the encryption passphrase and confirm passphrase and type have been set in the storage object
$self->optionTestSet(CFGOPT_REPO_CIPHER_PASS, 'x');
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
$self->testResult(sub {storageRepo({strStanza => $strStanzaEncrypt})->cipherType() eq
CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC}, true, 'encryption type set');
$self->testResult(sub {storageRepo({strStanza => $strStanzaEncrypt})->cipherPassUser() eq 'x'}, true,
'encryption passphrase set');
# Cannot change encryption after it has been set (cached values not reset)
$self->optionTestClear(CFGOPT_REPO_CIPHER_TYPE);
$self->optionTestClear(CFGOPT_REPO_CIPHER_PASS);
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
$self->testResult(sub {storageRepo({strStanza => $strStanzaEncrypt})->cipherType() eq
CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC}, true, 'encryption type not reset');
$self->testResult(sub {storageRepo({strStanza => $strStanzaEncrypt})->cipherPassUser() eq 'x'}, true,
'encryption passphrase not reset');
}
}

View File

@ -1,475 +0,0 @@
####################################################################################################################################
# Tests for Storage::Local module
####################################################################################################################################
package pgBackRestTest::Module::Storage::StorageLocalPerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use pgBackRest::Config::Config;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::LibC qw(:crypto);
use pgBackRest::Storage::Filter::Sha;
use pgBackRest::Storage::Base;
use pgBackRest::Storage::Local;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Env::Host::HostBackupTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# initModule - common objects and variables used by all tests.
####################################################################################################################################
sub initModule
{
my $self = shift;
# Local path
$self->{strPathLocal} = $self->testPath() . '/local';
# Create the dynamic rule
my $fnRule = sub
{
my $strRule = shift;
my $strFile = shift;
my $xData = shift;
if ($strRule eq '<fn-rule-1>')
{
return "fn-rule-1/${xData}" . (defined($strFile) ? "/${strFile}" : '');
}
else
{
return 'fn-rule-2/' . (defined($strFile) ? "${strFile}/${strFile}" : 'no-file');
}
};
# Create the rule hash
my $hRule =
{
'<static-rule>' => 'static-rule-path',
'<fn-rule-1>' =>
{
fnRule => $fnRule,
xData => 'test',
},
'<fn-rule-2>' =>
{
fnRule => $fnRule,
},
};
# Create local storage
$self->{oStorageLocal} = new pgBackRest::Storage::Local(
$self->pathLocal(), new pgBackRest::Storage::Posix::Driver(), {hRule => $hRule, bAllowTemp => false});
# Create encrypted storage
$self->{oStorageEncrypt} = new pgBackRest::Storage::Local(
$self->testPath(), new pgBackRest::Storage::Posix::Driver(),
{hRule => $hRule, bAllowTemp => false, strCipherType => CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC});
# Remote path
$self->{strPathRemote} = $self->testPath() . '/remote';
# Create the repo path so the remote won't complain that it's missing
mkdir($self->pathRemote())
or confess &log(ERROR, "unable to create repo directory '" . $self->pathRemote() . qw{'});
# Remove repo path now that the remote is created
rmdir($self->{strPathRemote})
or confess &log(ERROR, "unable to remove repo directory '" . $self->pathRemote() . qw{'});
# Create remote storage
$self->{oStorageRemote} = new pgBackRest::Storage::Local(
$self->pathRemote(), new pgBackRest::Storage::Posix::Driver(), {hRule => $hRule});
}
####################################################################################################################################
# initTest - initialization before each test
####################################################################################################################################
sub initTest
{
my $self = shift;
executeTest(
'ssh ' . $self->backrestUser() . '\@' . $self->host() . ' mkdir -m 700 ' . $self->pathRemote(), {bSuppressStdErr => true});
executeTest('mkdir -m 700 ' . $self->pathLocal());
}
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Define test file
my $strFile = 'file.txt';
my $strFileCopy = 'file.txt.copy';
my $strFileHash = 'bbbcf2c59433f68f22376cd2439d6cd309378df6';
my $strFileContent = 'TESTDATA';
my $iFileSize = length($strFileContent);
################################################################################################################################
if ($self->begin("pathGet()"))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$self->storageLocal()->pathGet('<static-rule>/test', {bTemp => true})},
ERROR_ASSERT, "temp file not supported for storage '" . $self->storageLocal()->pathBase() . "'");
$self->testException(
sub {$self->storageRemote()->pathGet('<static-rule>', {bTemp => true})},
ERROR_ASSERT, 'file part must be defined when temp file specified');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageRemote()->pathGet('/file', {bTemp => true})}, "/file.tmp", 'absolute path temp');
$self->testResult(sub {$self->storageRemote()->pathGet('/file')}, "/file", 'absolute path file');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->pathGet('file')}, $self->storageLocal()->pathBase() . '/file', 'relative path');
$self->testResult(
sub {$self->storageRemote()->pathGet('file', {bTemp => true})},
$self->storageRemote()->pathBase() . '/file.tmp', 'relative path temp');
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$self->storageLocal()->pathGet('<static-rule/file')}, ERROR_ASSERT, "found < but not > in '<static-rule/file'");
$self->testException(
sub {$self->storageLocal()->pathGet('<bogus-rule>')}, ERROR_ASSERT, "storage rule '<bogus-rule>' does not exist");
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->pathGet('<static-rule>/file')},
$self->storageLocal()->pathBase() . '/static-rule-path/file', 'static rule file');
$self->testResult(
sub {$self->storageLocal()->pathGet('<static-rule>')},
$self->storageLocal()->pathBase() . '/static-rule-path', 'static rule path');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->pathGet('<fn-rule-1>/file')},
$self->storageLocal()->pathBase() . '/fn-rule-1/test/file', 'function rule 1 file');
$self->testResult(
sub {$self->storageLocal()->pathGet('<fn-rule-2>/file')},
$self->storageLocal()->pathBase() . '/fn-rule-2/file/file', 'function rule 2 file');
$self->testResult(
sub {$self->storageLocal()->pathGet('<fn-rule-1>')},
$self->storageLocal()->pathBase() . '/fn-rule-1/test', 'function rule 1 path');
$self->testResult(
sub {$self->storageLocal()->pathGet('<fn-rule-2>')},
$self->storageLocal()->pathBase() . '/fn-rule-2/no-file', 'function rule 2 no file');
}
################################################################################################################################
if ($self->begin('openWrite()'))
{
#---------------------------------------------------------------------------------------------------------------------------
my $oFileIo = $self->testResult(sub {$self->storageLocal()->openWrite($strFile)}, '[object]', 'open write');
$self->testResult(sub {$oFileIo->write(\$strFileContent)}, $iFileSize, "write $iFileSize bytes");
$self->testResult(sub {$oFileIo->close()}, true, 'close');
# Check that it is not encrypted
$self->testResult(sub {$self->storageLocal()->encrypted($strFile)}, false, 'test storage not encrypted');
}
################################################################################################################################
if ($self->begin('put()'))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->put($self->storageLocal()->openWrite($strFile))}, 0, 'put empty');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->put($strFile)}, 0, 'put empty (all defaults)');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->put($self->storageLocal()->openWrite($strFile), $strFileContent)}, $iFileSize, 'put');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->put($self->storageLocal()->openWrite($strFile), \$strFileContent)}, $iFileSize,
'put reference');
}
################################################################################################################################
if ($self->begin('openRead()'))
{
my $tContent;
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->openRead($strFile, {bIgnoreMissing => true})}, undef, 'ignore missing');
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$self->storageLocal()->openRead($strFile)}, ERROR_FILE_MISSING,
"unable to open '" . $self->storageLocal()->pathBase() . "/${strFile}': No such file or directory");
#---------------------------------------------------------------------------------------------------------------------------
executeTest('sudo touch ' . $self->pathLocal() . "/${strFile} && sudo chmod 700 " . $self->pathLocal() . "/${strFile}");
$self->testException(
sub {$self->storageLocal()->openRead($strFile)}, ERROR_FILE_OPEN,
"unable to open '" . $self->storageLocal()->pathBase() . "/${strFile}': Permission denied");
executeTest('sudo rm ' . $self->pathLocal() . "/${strFile}");
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->put($self->storageLocal()->openWrite($strFile), $strFileContent);
my $oFileIo = $self->testResult(sub {$self->storageLocal()->openRead($strFile)}, '[object]', 'open read');
$self->testResult(sub {$oFileIo->read(\$tContent, $iFileSize)}, $iFileSize, "read $iFileSize bytes");
$self->testResult($tContent, $strFileContent, ' check read');
#---------------------------------------------------------------------------------------------------------------------------
$oFileIo = $self->testResult(
sub {$self->storageLocal()->openRead($strFile, {rhyFilter => [{strClass => STORAGE_FILTER_SHA}]})}, '[object]',
'open read + checksum');
undef($tContent);
$self->testResult(sub {$oFileIo->read(\$tContent, $iFileSize)}, $iFileSize, "read $iFileSize bytes");
$self->testResult(sub {$oFileIo->close()}, true, 'close');
$self->testResult($tContent, $strFileContent, ' check read');
$self->testResult($oFileIo->result(STORAGE_FILTER_SHA), cryptoHashOne('sha1', $strFileContent), ' check hash');
}
################################################################################################################################
if ($self->begin('get()'))
{
my $tBuffer;
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->get($self->storageLocal()->openRead($strFile, {bIgnoreMissing => true}))}, undef,
'get missing');
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->put($strFile);
$self->testResult(sub {${$self->storageLocal()->get($strFile)}}, undef, 'get empty');
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->put($strFile, $strFileContent);
$self->testResult(sub {${$self->storageLocal()->get($strFile)}}, $strFileContent, 'get');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {${$self->storageLocal()->get($self->storageLocal()->openRead($strFile))}}, $strFileContent, 'get from io');
}
################################################################################################################################
if ($self->begin('hashSize()'))
{
my $tBuffer;
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->put($strFile, $strFileContent)}, 8, 'put');
$self->testResult(
sub {$self->storageLocal()->hashSize($strFile)},
qw{(} . cryptoHashOne('sha1', $strFileContent) . ', ' . $iFileSize . qw{)}, ' check hash/size');
$self->testResult(
sub {$self->storageLocal()->hashSize(BOGUS, {bIgnoreMissing => true})}, "([undef], [undef])",
' check missing hash/size');
}
################################################################################################################################
if ($self->begin('copy()'))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$self->storageLocal()->copy($self->storageLocal()->openRead($strFile), $strFileCopy)}, ERROR_FILE_MISSING,
"unable to open '" . $self->storageLocal()->pathBase() . "/${strFile}': No such file or directory");
$self->testResult(
sub {$self->storageLocal()->exists($strFileCopy)}, false, ' destination does not exist');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->copy(
$self->storageLocal()->openRead($strFile, {bIgnoreMissing => true}),
$self->storageLocal()->openWrite($strFileCopy))},
false, 'missing source io');
$self->testResult(
sub {$self->storageLocal()->exists($strFileCopy)}, false, ' destination does not exist');
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$self->storageLocal()->copy($self->storageLocal()->openRead($strFile), $strFileCopy)}, ERROR_FILE_MISSING,
"unable to open '" . $self->storageLocal()->pathBase() . "/${strFile}': No such file or directory");
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->put($strFile, $strFileContent);
$self->testResult(sub {$self->storageLocal()->copy($strFile, $strFileCopy)}, true, 'copy filename->filename');
$self->testResult(sub {${$self->storageLocal()->get($strFileCopy)}}, $strFileContent, ' check copy');
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->remove($strFileCopy);
$self->testResult(
sub {$self->storageLocal()->copy($self->storageLocal()->openRead($strFile), $strFileCopy)}, true, 'copy io->filename');
$self->testResult(sub {${$self->storageLocal()->get($strFileCopy)}}, $strFileContent, ' check copy');
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->remove($strFileCopy);
$self->testResult(
sub {$self->storageLocal()->copy(
$self->storageLocal()->openRead($strFile), $self->storageLocal()->openWrite($strFileCopy))},
true, 'copy io->io');
$self->testResult(sub {${$self->storageLocal()->get($strFileCopy)}}, $strFileContent, ' check copy');
}
################################################################################################################################
if ($self->begin('info()'))
{
$self->testResult(sub {$self->storageLocal()->info($self->{strPathLocal})}, "[object]", 'stat dir successfully');
$self->testException(sub {$self->storageLocal()->info($strFile)}, ERROR_FILE_MISSING,
"unable to stat '". $self->{strPathLocal} . "/" . $strFile ."': No such file or directory");
}
################################################################################################################################
if ($self->begin('pathCreate()'))
{
my $strTestPath = $self->{strPathLocal} . "/" . BOGUS;
$self->testResult(sub {$self->storageLocal()->pathCreate($strTestPath)}, "[undef]",
"test creation of path " . $strTestPath);
$self->testException(sub {$self->storageLocal()->pathCreate($strTestPath)}, ERROR_PATH_EXISTS,
"unable to create path '". $strTestPath. "' because it already exists");
$self->testResult(sub {$self->storageLocal()->pathCreate($strTestPath, {bIgnoreExists => true})}, "[undef]",
"ignore path exists");
}
################################################################################################################################
if ($self->begin('encryption'))
{
my $strCipherPass = 'x';
$self->testResult(sub {cryptoHashOne('sha1', $strFileContent)}, $strFileHash, 'hash check contents to be written');
# Error when passphrase not passed
#---------------------------------------------------------------------------------------------------------------------------
my $oFileIo = $self->testException(sub {$self->storageEncrypt()->openWrite($strFile)},
ERROR_ASSERT, 'tCipherPass is required in Storage::Filter::CipherBlock->new');
# Write an encrypted file
#---------------------------------------------------------------------------------------------------------------------------
$oFileIo = $self->testResult(sub {$self->storageEncrypt()->openWrite($strFile, {strCipherPass => $strCipherPass})},
'[object]', 'open write');
my $iWritten = $oFileIo->write(\$strFileContent);
$self->testResult(sub {$oFileIo->close()}, true, ' close');
# Check that it is encrypted and valid for the repo encryption type
$self->testResult(sub {$self->storageEncrypt()->encryptionValid($self->storageEncrypt()->encrypted($strFile))}, true,
' test storage encrypted and valid');
$self->testResult(
sub {cryptoHashOne('sha1', ${storageTest()->get($strFile)}) ne $strFileHash}, true, ' check written sha1 different');
# Error when passphrase not passed
#---------------------------------------------------------------------------------------------------------------------------
$oFileIo = $self->testException(sub {$self->storageEncrypt()->openRead($strFile)},
ERROR_ASSERT, 'tCipherPass is required in Storage::Filter::CipherBlock->new');
# Read it and confirm it decrypts and is same as original content
#---------------------------------------------------------------------------------------------------------------------------
$oFileIo = $self->testResult(sub {$self->storageEncrypt()->openRead($strFile, {strCipherPass => $strCipherPass})},
'[object]', 'open read and decrypt');
my $strContent;
$oFileIo->read(\$strContent, $iWritten);
$self->testResult(sub {$oFileIo->close()}, true, ' close');
$self->testResult($strContent, $strFileContent, ' decrypt read equal orginal contents');
# Copy
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageEncrypt()->copy(
$self->storageEncrypt()->openRead($strFile, {strCipherPass => $strCipherPass}),
$self->storageEncrypt()->openWrite($strFileCopy, {strCipherPass => $strCipherPass}))},
true, 'copy - decrypt/encrypt');
$self->testResult(
sub {cryptoHashOne('sha1', ${$self->storageEncrypt()->get($strFileCopy, {strCipherPass => $strCipherPass})})},
$strFileHash, ' check decrypted copy file sha1 same as original plaintext file');
# Write an empty encrypted file
#---------------------------------------------------------------------------------------------------------------------------
my $strFileZero = 'file-0.txt';
my $strZeroContent = '';
$oFileIo = $self->testResult(
sub {$self->storageEncrypt()->openWrite($strFileZero, {strCipherPass => $strCipherPass})}, '[object]',
'open write for zero');
$self->testResult(sub {$oFileIo->write(\$strZeroContent)}, 0, ' zero written');
$self->testResult(sub {$oFileIo->close()}, true, ' close');
$self->testResult(sub {$self->storageEncrypt()->encrypted($strFile)}, true, ' test empty file encrypted');
# Write an unencrypted file to the encrypted storage and check if the file is valid for that storage
#---------------------------------------------------------------------------------------------------------------------------
my $strFileTest = $self->testPath() . qw{/} . 'test.file.txt';
# Create empty file
executeTest("touch ${strFileTest}");
$self->testResult(sub {$self->storageEncrypt()->encrypted($strFileTest)}, false, 'empty file so not encrypted');
# Add unencrypted content to the file
executeTest("echo -n '${strFileContent}' | tee ${strFileTest}");
$self->testResult(sub {$self->storageEncrypt()->encryptionValid($self->storageEncrypt()->encrypted($strFileTest))}, false,
'storage encryption and unencrypted file format do not match');
# Unencrypted file valid in unencrypted storage
$self->testResult(sub {$self->storageLocal()->encryptionValid($self->storageLocal()->encrypted($strFileTest))}, true,
'unencrypted file valid in unencrypted storage');
# Prepend encryption Magic Signature and test encrypted file in unencrypted storage not valid
executeTest('echo "' . CIPHER_MAGIC . '$(cat ' . $strFileTest . ')" > ' . $strFileTest);
$self->testResult(sub {$self->storageLocal()->encryptionValid($self->storageLocal()->encrypted($strFileTest))}, false,
'storage unencrypted and encrypted file format do not match');
# Test a file that does not exist
#---------------------------------------------------------------------------------------------------------------------------
$strFileTest = $self->testPath() . qw{/} . 'testfile';
$self->testException(sub {$self->storageEncrypt()->encrypted($strFileTest)}, ERROR_FILE_MISSING,
"unable to open '" . $strFileTest . "': No such file or directory");
$self->testResult(sub {$self->storageEncrypt()->encrypted($strFileTest, {bIgnoreMissing => true})}, true,
'encryption for ignore missing file returns encrypted for encrypted storage');
$self->testResult(sub {$self->storageLocal()->encrypted($strFileTest, {bIgnoreMissing => true})}, false,
'encryption for ignore missing file returns unencrypted for unencrypted storage');
}
}
####################################################################################################################################
# Getters
####################################################################################################################################
sub host {return '127.0.0.1'}
sub pathLocal {return shift->{strPathLocal}};
sub pathRemote {return shift->{strPathRemote}};
sub protocolLocal {return shift->{oProtocolLocal}};
sub protocolRemote {return shift->{oProtocolRemote}};
sub storageLocal {return shift->{oStorageLocal}};
sub storageEncrypt {return shift->{oStorageEncrypt}};
sub storageRemote {return shift->{oStorageRemote}};
1;

View File

@ -0,0 +1,341 @@
####################################################################################################################################
# Tests for Storage::Local module
####################################################################################################################################
package pgBackRestTest::Module::Storage::StoragePerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use pgBackRest::Config::Config;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::LibC qw(:crypto);
use pgBackRest::Storage::Base;
use pgBackRestTest::Common::ContainerTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
use pgBackRestTest::Env::Host::HostBackupTest;
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Define test file
my $strFile = $self->testPath() . '/file.txt';
my $strFileCopy = $self->testPath() . '/file.txt.copy';
my $strFileHash = 'bbbcf2c59433f68f22376cd2439d6cd309378df6';
my $strFileContent = 'TESTDATA';
my $iFileSize = length($strFileContent);
# Create local storage
$self->{oStorageLocal} = new pgBackRest::Storage::Storage('<LOCAL>');
################################################################################################################################
if ($self->begin("pathGet()"))
{
$self->testResult(sub {$self->storageLocal()->pathGet('file')}, '/file', 'relative path');
$self->testResult(sub {$self->storageLocal()->pathGet('/file2')}, '/file2', 'absolute path');
}
################################################################################################################################
if ($self->begin('put()'))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->put($self->storageLocal()->openWrite($strFile))}, 0, 'put empty');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->put($strFile)}, 0, 'put empty (all defaults)');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->put($self->storageLocal()->openWrite($strFile), $strFileContent)}, $iFileSize, 'put');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->put($self->storageLocal()->openWrite($strFile), \$strFileContent)}, $iFileSize,
'put reference');
}
################################################################################################################################
if ($self->begin('get()'))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->get($self->storageLocal()->openRead($strFile, {bIgnoreMissing => true}))}, undef,
'get missing');
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->put($strFile);
$self->testResult(sub {${$self->storageLocal()->get($strFile)}}, undef, 'get empty');
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->put($strFile, $strFileContent);
$self->testResult(sub {${$self->storageLocal()->get($strFile)}}, $strFileContent, 'get');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {${$self->storageLocal()->get($self->storageLocal()->openRead($strFile))}}, $strFileContent, 'get from io');
}
################################################################################################################################
if ($self->begin('hashSize()'))
{
$self->testResult(
sub {$self->storageLocal()->put($strFile, $strFileContent)}, 8, 'put');
$self->testResult(
sub {$self->storageLocal()->hashSize($strFile)},
qw{(} . cryptoHashOne('sha1', $strFileContent) . ', ' . $iFileSize . qw{)}, ' check hash/size');
$self->testResult(
sub {$self->storageLocal()->hashSize(BOGUS, {bIgnoreMissing => true})}, "([undef], [undef])",
' check missing hash/size');
}
################################################################################################################################
if ($self->begin('copy()'))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$self->storageLocal()->copy($self->storageLocal()->openRead($strFile), $strFileCopy)}, ERROR_FILE_MISSING,
"unable to open missing file '${strFile}' for read");
$self->testResult(
sub {$self->storageLocal()->exists($strFileCopy)}, false, ' destination does not exist');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$self->storageLocal()->copy(
$self->storageLocal()->openRead($strFile, {bIgnoreMissing => true}),
$self->storageLocal()->openWrite($strFileCopy))},
false, 'missing source io');
$self->testResult(
sub {$self->storageLocal()->exists($strFileCopy)}, false, ' destination does not exist');
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$self->storageLocal()->copy($self->storageLocal()->openRead($strFile), $strFileCopy)}, ERROR_FILE_MISSING,
"unable to open missing file '${strFile}' for read");
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->put($strFile, $strFileContent);
$self->testResult(sub {$self->storageLocal()->copy($strFile, $strFileCopy)}, true, 'copy filename->filename');
$self->testResult(sub {${$self->storageLocal()->get($strFileCopy)}}, $strFileContent, ' check copy');
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->remove($strFileCopy);
$self->testResult(
sub {$self->storageLocal()->copy($self->storageLocal()->openRead($strFile), $strFileCopy)}, true, 'copy io->filename');
$self->testResult(sub {${$self->storageLocal()->get($strFileCopy)}}, $strFileContent, ' check copy');
#---------------------------------------------------------------------------------------------------------------------------
$self->storageLocal()->remove($strFileCopy);
$self->testResult(
sub {$self->storageLocal()->copy(
$self->storageLocal()->openRead($strFile), $self->storageLocal()->openWrite($strFileCopy))},
true, 'copy io->io');
$self->testResult(sub {${$self->storageLocal()->get($strFileCopy)}}, $strFileContent, ' check copy');
}
################################################################################################################################
if ($self->begin('exists()'))
{
$self->storageLocal()->put($self->testPath() . "/test.file");
$self->testResult(sub {$self->storageLocal()->exists($self->testPath() . "/test.file")}, true, 'existing file');
$self->testResult(sub {$self->storageLocal()->exists($self->testPath() . "/test.missing")}, false, 'missing file');
$self->testResult(sub {$self->storageLocal()->exists($self->testPath())}, false, 'path');
}
################################################################################################################################
if ($self->begin('info()'))
{
$self->testResult(
sub {$self->storageLocal()->info($self->testPath())},
"{group => " . $self->group() . ", mode => 0770, type => d, user => " . $self->pgUser() . "}",
'stat dir successfully');
$self->testException(sub {$self->storageLocal()->info(BOGUS)}, ERROR_FILE_OPEN,
"unable to get info for missing path/file '/bogus'");
}
################################################################################################################################
if ($self->begin("manifest() and list()"))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$self->storageLocal()->manifest($self->testPath() . '/missing')},
ERROR_PATH_MISSING, "unable to list file info for missing path '" . $self->testPath() . "/missing'");
#---------------------------------------------------------------------------------------------------------------------------
# Setup test data
executeTest('mkdir -m 750 ' . $self->testPath() . '/sub1');
executeTest('mkdir -m 750 ' . $self->testPath() . '/sub1/sub2');
executeTest('mkdir -m 750 ' . $self->testPath() . '/sub2');
executeTest("echo 'TESTDATA' > " . $self->testPath() . '/test.txt');
utime(1111111111, 1111111111, $self->testPath() . '/test.txt');
executeTest('chmod 1640 ' . $self->testPath() . '/test.txt');
executeTest("echo 'TESTDATA_' > ". $self->testPath() . '/sub1/test-sub1.txt');
utime(1111111112, 1111111112, $self->testPath() . '/sub1/test-sub1.txt');
executeTest('chmod 0640 ' . $self->testPath() . '/sub1/test-sub1.txt');
executeTest("echo 'TESTDATA__' > " . $self->testPath() . '/sub1/sub2/test-sub2.txt');
utime(1111111113, 1111111113, $self->testPath() . '/sub1/sub2/test-sub2.txt');
executeTest('chmod 0646 ' . $self->testPath() . '/sub1/test-sub1.txt');
executeTest('ln ' . $self->testPath() . '/test.txt ' . $self->testPath() . '/sub1/test-hardlink.txt');
executeTest('ln ' . $self->testPath() . '/test.txt ' . $self->testPath() . '/sub1/sub2/test-hardlink.txt');
executeTest('ln -s .. ' . $self->testPath() . '/sub1/test');
executeTest('chmod 0700 ' . $self->testPath() . '/sub1/test');
executeTest('ln -s ../.. ' . $self->testPath() . '/sub1/sub2/test');
executeTest('chmod 0750 ' . $self->testPath() . '/sub1/sub2/test');
executeTest('chmod 0770 ' . $self->testPath());
$self->testResult(
sub {$self->storageLocal()->manifest($self->testPath())},
'{. => {group => ' . $self->group() . ', mode => 0770, type => d, user => ' . $self->pgUser() . '}, ' .
'sub1 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
'sub1/sub2 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
'sub1/sub2/test => {group => ' . $self->group() . ', link_destination => ../.., type => l, user => ' .
$self->pgUser() . '}, ' .
'sub1/sub2/test-hardlink.txt => ' .
'{group => ' . $self->group() . ', mode => 0640, modification_time => 1111111111, size => 9, type => f, user => ' .
$self->pgUser() . '}, ' .
'sub1/sub2/test-sub2.txt => ' .
'{group => ' . $self->group() . ', mode => 0666, modification_time => 1111111113, size => 11, type => f, user => ' .
$self->pgUser() . '}, ' .
'sub1/test => {group => ' . $self->group() . ', link_destination => .., type => l, user => ' . $self->pgUser() . '}, ' .
'sub1/test-hardlink.txt => ' .
'{group => ' . $self->group() . ', mode => 0640, modification_time => 1111111111, size => 9, type => f, user => ' .
$self->pgUser() . '}, ' .
'sub1/test-sub1.txt => ' .
'{group => ' . $self->group() . ', mode => 0646, modification_time => 1111111112, size => 10, type => f, user => ' .
$self->pgUser() . '}, ' .
'sub2 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
'test.txt => ' .
'{group => ' . $self->group() . ', mode => 0640, modification_time => 1111111111, size => 9, type => f, user => ' .
$self->pgUser() . '}}',
'complete manifest');
$self->testResult(sub {$self->storageLocal()->list($self->testPath())}, "(sub1, sub2, test.txt)", "list");
$self->testResult(sub {$self->storageLocal()->list($self->testPath(), {strExpression => "2\$"})}, "sub2", "list");
$self->testResult(
sub {$self->storageLocal()->list($self->testPath(), {strSortOrder => 'reverse'})}, "(test.txt, sub2, sub1)",
"list reverse");
$self->testResult(sub {$self->storageLocal()->list($self->testPath() . "/sub2")}, "[undef]", "list empty");
$self->testResult(
sub {$self->storageLocal()->list($self->testPath() . "/sub99", {bIgnoreMissing => true})}, "[undef]", "list missing");
$self->testException(
sub {$self->storageLocal()->list($self->testPath() . "/sub99")}, ERROR_PATH_MISSING,
"unable to list files for missing path '" . $self->testPath() . "/sub99'");
}
################################################################################################################################
if ($self->begin('move()'))
{
my $strFileCopy = "${strFile}.copy";
my $strFileSub = $self->testPath() . '/sub/file.txt';
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$self->storageLocal()->move($strFile, $strFileCopy)}, ERROR_FILE_MOVE,
"unable to move '${strFile}' to '${strFile}.copy': No such file or directory");
}
################################################################################################################################
if ($self->begin('owner()'))
{
my $strFile = $self->testPath() . "/test.txt";
$self->testException(
sub {$self->storageLocal()->owner($strFile, 'root')}, ERROR_FILE_MISSING,
"unable to stat '${strFile}': No such file or directory");
executeTest("touch ${strFile}");
$self->testException(
sub {$self->storageLocal()->owner($strFile, BOGUS)}, ERROR_FILE_OWNER,
"unable to set ownership for '${strFile}' because user 'bogus' does not exist");
$self->testException(
sub {$self->storageLocal()->owner($strFile, undef, BOGUS)}, ERROR_FILE_OWNER,
"unable to set ownership for '${strFile}' because group 'bogus' does not exist");
$self->testResult(sub {$self->storageLocal()->owner($strFile)}, undef, "no ownership changes");
$self->testResult(sub {$self->storageLocal()->owner($strFile, TEST_USER)}, undef, "same user");
$self->testResult(sub {$self->storageLocal()->owner($strFile, undef, TEST_GROUP)}, undef, "same group");
$self->testResult(
sub {$self->storageLocal()->owner($strFile, TEST_USER, TEST_GROUP)}, undef,
"same user, group");
$self->testException(
sub {$self->storageLocal()->owner($strFile, 'root', undef)}, ERROR_FILE_OWNER,
"unable to set ownership for '${strFile}': Operation not permitted");
$self->testException(
sub {$self->storageLocal()->owner($strFile, undef, 'root')}, ERROR_FILE_OWNER,
"unable to set ownership for '${strFile}': Operation not permitted");
executeTest("sudo chown :root ${strFile}");
$self->testResult(
sub {$self->storageLocal()->owner($strFile, undef, TEST_GROUP)}, undef, "change group back from root");
}
################################################################################################################################
if ($self->begin('pathCreate()'))
{
my $strTestPath = $self->testPath() . "/" . BOGUS;
$self->testResult(sub {$self->storageLocal()->pathCreate($strTestPath)}, "[undef]",
"test creation of path " . $strTestPath);
$self->testException(sub {$self->storageLocal()->pathCreate($strTestPath)}, ERROR_PATH_CREATE,
"unable to create path '". $strTestPath. "'");
$self->testResult(sub {$self->storageLocal()->pathCreate($strTestPath, {bIgnoreExists => true})}, "[undef]",
"ignore path exists");
}
################################################################################################################################
if ($self->begin('pathExists()'))
{
$self->storageLocal()->put($self->testPath() . "/test.file");
$self->testResult(sub {$self->storageLocal()->pathExists($self->testPath() . "/test.file")}, false, 'existing file');
$self->testResult(sub {$self->storageLocal()->pathExists($self->testPath() . "/test.missing")}, false, 'missing file');
$self->testResult(sub {$self->storageLocal()->pathExists($self->testPath())}, true, 'path');
}
################################################################################################################################
if ($self->begin('pathSync()'))
{
$self->testResult(sub {$self->storageLocal()->pathSync($self->testPath())}, "[undef]", "test path sync");
}
}
####################################################################################################################################
# Getters
####################################################################################################################################
# sub host {return '127.0.0.1'}
# sub pathLocal {return shift->{strPathLocal}};
# sub pathRemote {return shift->{strPathRemote}};
sub storageLocal {return shift->{oStorageLocal}};
# sub storageEncrypt {return shift->{oStorageEncrypt}};
# sub storageRemote {return shift->{oStorageRemote}};
1;

View File

@ -1,444 +0,0 @@
####################################################################################################################################
# Posix Driver Tests
####################################################################################################################################
package pgBackRestTest::Module::Storage::StoragePosixPerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use File::Basename qw(basename dirname);
use IO::Socket::UNIX;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Storage::Posix::Driver;
use pgBackRestTest::Common::ContainerTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Test data
my $strFile = $self->testPath() . '/file.txt';
my $strFileContent = 'TESTDATA';
my $iFileLength = length($strFileContent);
my $iFileLengthHalf = int($iFileLength / 2);
# Test driver
my $oPosix = new pgBackRest::Storage::Posix::Driver();
################################################################################################################################
if ($self->begin('exists()'))
{
my $strPathSub = $self->testPath() . '/sub';
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$oPosix->exists($strFile)}, false, 'file');
#---------------------------------------------------------------------------------------------------------------------------
executeTest("sudo mkdir ${strPathSub} && sudo chmod 700 ${strPathSub}");
$self->testResult(
sub {$oPosix->pathExists($strPathSub)}, true, 'path');
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$oPosix->exists("${strPathSub}/file")}, ERROR_FILE_EXISTS,
"unable to test if file '${strPathSub}/file' exists: Permission denied");
}
################################################################################################################################
if ($self->begin("manifestList()"))
{
#---------------------------------------------------------------------------------------------------------------------------
my @stryFile = ('.', 'test.txt');
$self->testResult(
sub {$oPosix->manifestList($self->testPath(), \@stryFile)},
'{. => {group => ' . $self->group() . ', mode => 0770, type => d, user => ' . $self->pgUser() . '}}',
'skip missing file');
}
################################################################################################################################
if ($self->begin("manifestStat()"))
{
#---------------------------------------------------------------------------------------------------------------------------
my $strFile = $self->testPath() . '/test.txt';
$self->testResult(sub {$oPosix->manifestStat($strFile)}, '[undef]', 'ignore missing file');
#---------------------------------------------------------------------------------------------------------------------------
storageTest()->put($strFile, "TEST");
utime(1111111111, 1111111111, $strFile);
executeTest('chmod 1640 ' . $strFile);
$self->testResult(
sub {$oPosix->manifestStat($strFile)},
'{group => ' . $self->group() .
', mode => 1640, modification_time => 1111111111, size => 4, type => f, user => ' . $self->pgUser() . '}',
'stat file');
#---------------------------------------------------------------------------------------------------------------------------
my $strSocketFile = $self->testPath() . '/test.socket';
# Create a socket to test invalid files
my $oSocket = IO::Socket::UNIX->new(Type => SOCK_STREAM(), Local => $strSocketFile, Listen => 1);
$self->testException(
sub {$oPosix->manifestStat($strSocketFile)}, ERROR_FILE_INVALID,
"${strSocketFile} is not of type directory, file, or link");
# Cleanup socket
$oSocket->close();
storageTest()->remove($strSocketFile);
#---------------------------------------------------------------------------------------------------------------------------
my $strTestPath = $self->testPath() . '/public_dir';
storageTest()->pathCreate($strTestPath, {strMode => '0750'});
$self->testResult(
sub {$oPosix->manifestStat($strTestPath)},
'{group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}',
'stat directory');
#---------------------------------------------------------------------------------------------------------------------------
my $strTestLink = $self->testPath() . '/public_dir_link';
symlink($strTestPath, $strTestLink)
or confess &log(ERROR, "unable to create symlink from ${strTestPath} to ${strTestLink}");
$self->testResult(
sub {$oPosix->manifestStat($strTestLink)},
'{group => ' . $self->group() . ", link_destination => ${strTestPath}, type => l, user => " . $self->pgUser() . '}',
'stat link');
}
################################################################################################################################
if ($self->begin("manifestRecurse()"))
{
#---------------------------------------------------------------------------------------------------------------------------
my $strTestPath = $self->testPath() . '/public_dir';
my $strTestFile = "${strTestPath}/test.txt";
$self->testException(
sub {my $hManifest = {}; $oPosix->manifestRecurse($strTestFile, undef, 0, $hManifest); $hManifest},
ERROR_FILE_MISSING, "unable to stat '${strTestFile}': No such file or directory");
#---------------------------------------------------------------------------------------------------------------------------
storageTest()->pathCreate($strTestPath, {strMode => '0750'});
$self->testResult(
sub {my $hManifest = {}; $oPosix->manifestRecurse($strTestPath, undef, 0, $hManifest); $hManifest},
'{. => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}}',
'empty directory manifest');
#---------------------------------------------------------------------------------------------------------------------------
storageTest()->put($strTestFile, "TEST");
utime(1111111111, 1111111111, $strTestFile);
executeTest('chmod 0750 ' . $strTestFile);
storageTest()->pathCreate("${strTestPath}/sub", {strMode => '0750'});
$self->testResult(
sub {my $hManifest = {}; $oPosix->manifestRecurse(
$self->testPath(), basename($strTestPath), 1, $hManifest); $hManifest},
'{public_dir => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
'public_dir/sub => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
'public_dir/' . basename($strTestFile) . ' => {group => ' . $self->group() .
', mode => 0750, modification_time => 1111111111, size => 4, type => f, user => ' . $self->pgUser() . '}}',
'directory and file manifest');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {my $hManifest = {}; $oPosix->manifestRecurse($strTestFile, undef, 0, $hManifest); $hManifest},
'{' . basename($strTestFile) . ' => {group => ' . $self->group() .
', mode => 0750, modification_time => 1111111111, size => 4, type => f, user => ' . $self->pgUser() . '}}',
'single file manifest');
}
################################################################################################################################
if ($self->begin("manifest()"))
{
#---------------------------------------------------------------------------------------------------------------------------
my $strMissingFile = $self->testPath() . '/missing';
$self->testException(
sub {$oPosix->manifest($strMissingFile)},
ERROR_FILE_MISSING, "unable to stat '${strMissingFile}': No such file or directory");
#---------------------------------------------------------------------------------------------------------------------------
# Setup test data
executeTest('mkdir -m 750 ' . $self->testPath() . '/sub1');
executeTest('mkdir -m 750 ' . $self->testPath() . '/sub1/sub2');
executeTest("echo 'TESTDATA' > " . $self->testPath() . '/test.txt');
utime(1111111111, 1111111111, $self->testPath() . '/test.txt');
executeTest('chmod 1640 ' . $self->testPath() . '/test.txt');
executeTest("echo 'TESTDATA_' > ". $self->testPath() . '/sub1/test-sub1.txt');
utime(1111111112, 1111111112, $self->testPath() . '/sub1/test-sub1.txt');
executeTest('chmod 0640 ' . $self->testPath() . '/sub1/test-sub1.txt');
executeTest("echo 'TESTDATA__' > " . $self->testPath() . '/sub1/sub2/test-sub2.txt');
utime(1111111113, 1111111113, $self->testPath() . '/sub1/sub2/test-sub2.txt');
executeTest('chmod 0646 ' . $self->testPath() . '/sub1/test-sub1.txt');
executeTest('ln ' . $self->testPath() . '/test.txt ' . $self->testPath() . '/sub1/test-hardlink.txt');
executeTest('ln ' . $self->testPath() . '/test.txt ' . $self->testPath() . '/sub1/sub2/test-hardlink.txt');
executeTest('ln -s .. ' . $self->testPath() . '/sub1/test');
executeTest('chmod 0700 ' . $self->testPath() . '/sub1/test');
executeTest('ln -s ../.. ' . $self->testPath() . '/sub1/sub2/test');
executeTest('chmod 0750 ' . $self->testPath() . '/sub1/sub2/test');
executeTest('chmod 0770 ' . $self->testPath());
$self->testResult(
sub {$oPosix->manifest($self->testPath())},
'{. => {group => ' . $self->group() . ', mode => 0770, type => d, user => ' . $self->pgUser() . '}, ' .
'sub1 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
'sub1/sub2 => {group => ' . $self->group() . ', mode => 0750, type => d, user => ' . $self->pgUser() . '}, ' .
'sub1/sub2/test => {group => ' . $self->group() . ', link_destination => ../.., type => l, user => ' .
$self->pgUser() . '}, ' .
'sub1/sub2/test-hardlink.txt => ' .
'{group => ' . $self->group() . ', mode => 1640, modification_time => 1111111111, size => 9, type => f, user => ' .
$self->pgUser() . '}, ' .
'sub1/sub2/test-sub2.txt => ' .
'{group => ' . $self->group() . ', mode => 0666, modification_time => 1111111113, size => 11, type => f, user => ' .
$self->pgUser() . '}, ' .
'sub1/test => {group => ' . $self->group() . ', link_destination => .., type => l, user => ' . $self->pgUser() . '}, ' .
'sub1/test-hardlink.txt => ' .
'{group => ' . $self->group() . ', mode => 1640, modification_time => 1111111111, size => 9, type => f, user => ' .
$self->pgUser() . '}, ' .
'sub1/test-sub1.txt => ' .
'{group => ' . $self->group() . ', mode => 0646, modification_time => 1111111112, size => 10, type => f, user => ' .
$self->pgUser() . '}, ' .
'test.txt => ' .
'{group => ' . $self->group() . ', mode => 1640, modification_time => 1111111111, size => 9, type => f, user => ' .
$self->pgUser() . '}}',
'complete manifest');
}
################################################################################################################################
if ($self->begin('openRead() & Posix::FileRead'))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$oPosix->openRead($strFile)}, ERROR_FILE_MISSING, "unable to open '${strFile}': No such file or directory");
#---------------------------------------------------------------------------------------------------------------------------
executeTest("echo -n '${strFileContent}' | tee ${strFile}");
$self->testResult(
sub {$oPosix->openRead($strFile)}, '[object]', 'open read');
}
################################################################################################################################
if ($self->begin('openWrite() & Posix::FileWrite'))
{
my $tContent = $strFileContent;
#---------------------------------------------------------------------------------------------------------------------------
executeTest("echo -n '${strFileContent}' | tee ${strFile}");
executeTest("chmod 600 ${strFile} && sudo chown root:root ${strFile}");
$self->testException(
sub {new pgBackRest::Storage::Posix::FileRead($oPosix, $strFile)}, ERROR_FILE_OPEN,
"unable to open '${strFile}': Permission denied");
executeTest("sudo rm -rf ${strFile}");
#---------------------------------------------------------------------------------------------------------------------------
my $oPosixIo = $self->testResult(
sub {new pgBackRest::Storage::Posix::FileWrite($oPosix, $strFile)}, '[object]', 'open');
$tContent = undef;
$self->testException(
sub {$oPosixIo->write(\$tContent)}, ERROR_FILE_WRITE, "unable to write to '${strFile}': Use of uninitialized value");
$tContent = substr($strFileContent, 0, $iFileLengthHalf);
$self->testResult(
sub {$oPosixIo->write(\$tContent)}, $iFileLengthHalf, 'write part 1');
$tContent = substr($strFileContent, $iFileLengthHalf);
$self->testResult(
sub {$oPosixIo->write(\$tContent)}, $iFileLength - $iFileLengthHalf,
'write part 2');
$oPosixIo->close();
$tContent = undef;
$self->testResult(
sub {(new pgBackRest::Storage::Posix::FileRead($oPosix, $strFile))->read(\$tContent, $iFileLength)},
$iFileLength, 'check write content length');
$self->testResult($tContent, $strFileContent, 'check write content');
#---------------------------------------------------------------------------------------------------------------------------
$oPosixIo = $self->testResult(
sub {new pgBackRest::Storage::Posix::FileWrite(
$oPosix, "${strFile}.atomic", {bAtomic => true, strMode => '0666', lTimestamp => time(), bSync => false})},
'[object]', 'open');
$self->testResult(sub {$oPosixIo->write(\$tContent, $iFileLength)}, $iFileLength, 'write');
$self->testResult(sub {$oPosixIo->close()}, true, 'close');
$self->testResult(sub {${storageTest()->get("${strFile}.atomic")}}, $strFileContent, 'check content');
#---------------------------------------------------------------------------------------------------------------------------
$oPosixIo = $self->testResult(
sub {new pgBackRest::Storage::Posix::FileWrite($oPosix, $strFile)}, '[object]', 'open');
$self->testResult(sub {$oPosixIo->close()}, true, 'close');
undef($oPosixIo);
# Test that a premature destroy (from error or otherwise) does not rename the file
#---------------------------------------------------------------------------------------------------------------------------
my $strFileAbort = $self->testPath() . '/file-abort.txt';
my $strFileAbortTmp = "${strFileAbort}.tmp";
$oPosixIo = $self->testResult(
sub {new pgBackRest::Storage::Posix::FileWrite($oPosix, $strFileAbort, {bAtomic => true})}, '[object]', 'open');
$oPosixIo->write(\$strFileContent);
undef($oPosixIo);
$self->testResult(sub {$oPosix->exists($strFileAbort)}, false, 'destination file does not exist');
$self->testResult(sub {$oPosix->exists($strFileAbortTmp)}, true, 'destination file tmp exists');
#---------------------------------------------------------------------------------------------------------------------------
$oPosixIo = $self->testResult(
sub {new pgBackRest::Storage::Posix::FileWrite($oPosix, $strFile, {lTimestamp => time()})}, '[object]', 'open');
$self->testResult(sub {$oPosixIo->write(\$strFileContent, $iFileLength)}, $iFileLength, 'write');
executeTest("rm -f $strFile");
$self->testException(
sub {$oPosixIo->close()}, ERROR_FILE_WRITE, "unable to set time for '${strFile}': No such file or directory");
}
################################################################################################################################
if ($self->begin('owner()'))
{
my $strFile = $self->testPath() . "/test.txt";
$self->testException(
sub {$oPosix->owner($strFile, {strUser => 'root'})}, ERROR_FILE_MISSING,
"unable to stat '${strFile}': No such file or directory");
executeTest("touch ${strFile}");
$self->testException(
sub {$oPosix->owner($strFile, {strUser => BOGUS})}, ERROR_FILE_OWNER,
"unable to set ownership for '${strFile}' because user 'bogus' does not exist");
$self->testException(
sub {$oPosix->owner($strFile, {strGroup => BOGUS})}, ERROR_FILE_OWNER,
"unable to set ownership for '${strFile}' because group 'bogus' does not exist");
$self->testResult(sub {$oPosix->owner($strFile)}, undef, "no ownership changes");
$self->testResult(sub {$oPosix->owner($strFile, {strUser => TEST_USER})}, undef, "same user");
$self->testResult(sub {$oPosix->owner($strFile, {strGroup => TEST_GROUP})}, undef, "same group");
$self->testResult(
sub {$oPosix->owner($strFile, {strUser => TEST_USER, strGroup => TEST_GROUP})}, undef, "same user, group");
$self->testException(
sub {$oPosix->owner($strFile, {strUser => 'root'})}, ERROR_FILE_OWNER,
"unable to set ownership for '${strFile}': Operation not permitted");
$self->testException(
sub {$oPosix->owner($strFile, {strGroup => 'root'})}, ERROR_FILE_OWNER,
"unable to set ownership for '${strFile}': Operation not permitted");
executeTest("sudo chown :root ${strFile}");
$self->testResult(
sub {$oPosix->owner($strFile, {strGroup => TEST_GROUP})}, undef, "change group back from root");
}
################################################################################################################################
if ($self->begin('pathCreate()'))
{
my $strPathParent = $self->testPath() . '/parent';
my $strPathSub = "${strPathParent}/sub1/sub2";
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oPosix->pathCreate($strPathParent)}, undef, 'parent path');
$self->testResult(
sub {$oPosix->pathExists($strPathParent)}, true, ' check path');
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$oPosix->pathCreate($strPathParent)}, ERROR_PATH_EXISTS,
"unable to create path '${strPathParent}' because it already exists");
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$oPosix->pathCreate($strPathParent, {bIgnoreExists => true})}, undef, 'path already exists');
#---------------------------------------------------------------------------------------------------------------------------
executeTest("sudo chown root:root ${strPathParent} && sudo chmod 700 ${strPathParent}");
$self->testException(
sub {$oPosix->pathCreate($strPathSub)}, ERROR_PATH_CREATE,
"unable to create path '${strPathSub}': Permission denied");
#---------------------------------------------------------------------------------------------------------------------------
executeTest("rmdir ${strPathParent}");
$self->testException(
sub {$oPosix->pathCreate($strPathSub)}, ERROR_PATH_MISSING,
"unable to create path '${strPathSub}' because parent does not exist");
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$oPosix->pathCreate($strPathSub, {bCreateParent => true})}, undef, 'path with parents');
$self->testResult(
sub {$oPosix->pathExists($strPathSub)}, true, ' check path');
}
################################################################################################################################
if ($self->begin('move()'))
{
my $strFileCopy = "${strFile}.copy";
my $strFileSub = $self->testPath() . '/sub/file.txt';
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$oPosix->move($strFile, $strFileCopy)}, ERROR_FILE_MISSING,
"unable to move '${strFile}' because it is missing");
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {storageTest()->put($strFile, $strFileContent)}, $iFileLength, 'put');
$self->testResult(
sub {$oPosix->move($strFile, $strFileCopy)}, undef, 'simple move');
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {$oPosix->move($strFileCopy, $strFileSub)}, ERROR_PATH_MISSING,
"unable to move '${strFileCopy}' to missing path '" . dirname($strFileSub) . "'");
#---------------------------------------------------------------------------------------------------------------------------
executeTest('sudo mkdir ' . dirname($strFileSub) . ' && sudo chmod 700 ' . dirname($strFileSub));
$self->testException(
sub {$oPosix->move($strFileCopy, $strFileSub)}, ERROR_FILE_MOVE,
"unable to move '${strFileCopy}' to '${strFileSub}': Permission denied");
executeTest('sudo rmdir ' . dirname($strFileSub));
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$oPosix->move($strFileCopy, $strFileSub, {bCreatePath => true})}, undef, 'create parent path');
}
}
1;

View File

@ -1,167 +0,0 @@
####################################################################################################################################
# S3 Authentication Tests
####################################################################################################################################
package pgBackRestTest::Module::Storage::StorageS3AuthPerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use POSIX qw(strftime);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Common::Wait;
use pgBackRest::Storage::S3::Auth;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
################################################################################################################################
if ($self->begin('s3DateTime'))
{
$self->testResult(sub {s3DateTime(1491267845)}, '20170404T010405Z', 'format date/time');
#---------------------------------------------------------------------------------------------------------------------------
waitRemainder();
$self->testResult(sub {s3DateTime()}, strftime("%Y%m%dT%H%M%SZ", gmtime()), 'format current date/time');
}
################################################################################################################################
if ($self->begin('s3CanonicalRequest'))
{
$self->testResult(
sub {s3CanonicalRequest(
'GET', qw(/), 'list-type=2',
{'host' => 'bucket.s3.amazonaws.com', 'x-amz-date' => '20170606T121212Z',
'x-amz-content-sha256' => '705636ecdedffc09f140497bcac3be1e8d069008ecc6a8029e104d6291b4e4e9'},
'705636ecdedffc09f140497bcac3be1e8d069008ecc6a8029e104d6291b4e4e9')},
"(GET\n/\nlist-type=2\nhost:bucket.s3.amazonaws.com\n" .
"x-amz-content-sha256:705636ecdedffc09f140497bcac3be1e8d069008ecc6a8029e104d6291b4e4e9\n" .
"x-amz-date:20170606T121212Z\n\nhost;x-amz-content-sha256;x-amz-date\n" .
'705636ecdedffc09f140497bcac3be1e8d069008ecc6a8029e104d6291b4e4e9' .
', host;x-amz-content-sha256;x-amz-date)',
'canonical request');
#---------------------------------------------------------------------------------------------------------------------------
$self->testException(
sub {s3CanonicalRequest(
'GET', qw(/), 'list-type=2', {'Host' => 'bucket.s3.amazonaws.com'},
'705636ecdedffc09f140497bcac3be1e8d069008ecc6a8029e104d6291b4e4e9')},
ERROR_ASSERT, "header 'Host' must be lower case");
}
################################################################################################################################
if ($self->begin('s3SigningKey'))
{
$self->testResult(
sub {unpack('H*', s3SigningKey('20170412', 'us-east-1', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'))},
'705636ecdedffc09f140497bcac3be1e8d069008ecc6a8029e104d6291b4e4e9', 'signing key');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {unpack('H*', s3SigningKey('20170412', 'us-east-1', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'))},
'705636ecdedffc09f140497bcac3be1e8d069008ecc6a8029e104d6291b4e4e9', 'same signing key from cache');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {unpack('H*', s3SigningKey('20170505', 'us-west-1', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'))},
'c1a1cb590bbc38ba789c8e5695a1ec0cd7fd44c6949f922e149005a221524c09', 'new signing key');
}
################################################################################################################################
if ($self->begin('s3StringToSign'))
{
$self->testResult(
sub {s3StringToSign(
'20170412T141414Z', 'us-east-1', '705636ecdedffc09f140497bcac3be1e8d069008ecc6a8029e104d6291b4e4e9')},
"AWS4-HMAC-SHA256\n20170412T141414Z\n20170412/us-east-1/s3/aws4_request\n" .
"705636ecdedffc09f140497bcac3be1e8d069008ecc6a8029e104d6291b4e4e9",
'string to sign');
}
################################################################################################################################
if ($self->begin('s3AuthorizationHeader'))
{
$self->testResult(
sub {s3AuthorizationHeader(
'us-east-1', 'bucket.s3.amazonaws.com', 'GET', qw(/), 'list-type=2', '20170606T121212Z',
{'authorization' => BOGUS, 'host' => 'bucket.s3.amazonaws.com', 'x-amz-date' => '20170606T121212Z'},
'AKIAIOSFODNN7EXAMPLE', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', undef,
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')},
'({authorization => AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20170606/us-east-1/s3/aws4_request,' .
'SignedHeaders=host;x-amz-content-sha256;x-amz-date,' .
'Signature=cb03bf1d575c1f8904dabf0e573990375340ab293ef7ad18d049fc1338fd89b3,' .
' host => bucket.s3.amazonaws.com,' .
' x-amz-content-sha256 => e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,' .
' x-amz-date => 20170606T121212Z}, ' .
"GET\n" .
"/\n" .
"list-type=2\n" .
"host:bucket.s3.amazonaws.com\n" .
"x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n" .
"x-amz-date:20170606T121212Z\n" .
"\n" .
"host;x-amz-content-sha256;x-amz-date\n" .
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, " .
"host;x-amz-content-sha256;x-amz-date, " .
"AWS4-HMAC-SHA256\n" .
"20170606T121212Z\n" .
"20170606/us-east-1/s3/aws4_request\n" .
"4f2d4ee971f579e60ba6b3895e87434e17b1260f04392f02b512c1e8bada72dd)",
'authorization header request');
$self->testResult(
sub {s3AuthorizationHeader(
'us-east-1', 'bucket.s3.amazonaws.com', 'GET', qw(/), 'list-type=2', '20170606T121212Z',
{'authorization' => BOGUS, 'host' => 'bucket.s3.amazonaws.com', 'x-amz-date' => '20170606T121212Z'},
'AKIAIOSFODNN7EXAMPLE', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
'AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQW' .
'LWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGd' .
'QrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU' .
'9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz' .
'+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA==',
'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855')},
'({authorization => AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20170606/us-east-1/s3/aws4_request,' .
'SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token,' .
'Signature=c12565bf5d7e0ef623f76d66e09e5431aebef803f6a25a01c586525f17e474a3,' .
' host => bucket.s3.amazonaws.com,' .
' x-amz-content-sha256 => e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855,' .
' x-amz-date => 20170606T121212Z, x-amz-security-token => AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4H' .
'IZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZ' .
'ampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+sc' .
'qKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA==}, ' .
"GET\n" .
"/\n" .
"list-type=2\n" .
"host:bucket.s3.amazonaws.com\n" .
"x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n" .
"x-amz-date:20170606T121212Z\n" .
"x-amz-security-token:AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIe" .
"oIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlR" .
"d8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJab" .
"IQwj2ICCR/oLxBA==\n" .
"\n" .
"host;x-amz-content-sha256;x-amz-date;x-amz-security-token\n" .
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, " .
"host;x-amz-content-sha256;x-amz-date;x-amz-security-token, " .
"AWS4-HMAC-SHA256\n" .
"20170606T121212Z\n" .
"20170606/us-east-1/s3/aws4_request\n" .
"c171e7a68355ef4e0e6e1003d2d4a79a7b06e7424e3000ba619f5f7882a3251e)",
'authorization header request with token');
}
}
1;

View File

@ -1,113 +0,0 @@
####################################################################################################################################
# S3 SSL Certificate Tests
#
# Verify that SSL certificate validation works on live S3 servers.
####################################################################################################################################
package pgBackRestTest::Module::Storage::StorageS3CertPerlTest;
use parent 'pgBackRestTest::Env::ConfigEnvTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Storable qw(dclone);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Log;
use pgBackRest::Common::Wait;
use pgBackRest::Config::Config;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRestTest::Common::RunTest;
use pgBackRestTest::Common::VmTest;
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Use long random string so bucket lookups will fail and expose access errors
my $strBucket = 'bnBfyKpXR8ZqQY5RXszxemRgvtmjXd4tf5HkFYhTpT9BndUCYMDy5NCCyRz';
my $strEndpoint = 's3-us-west-2.amazonaws.com';
my $strRegion = 'us-west-2';
# Options
$self->optionTestSet(CFGOPT_REPO_TYPE, CFGOPTVAL_REPO_TYPE_S3);
$self->optionTestSet(CFGOPT_REPO_S3_KEY, BOGUS);
$self->optionTestSet(CFGOPT_REPO_S3_KEY_SECRET, BOGUS);
$self->optionTestSet(CFGOPT_REPO_S3_TOKEN, BOGUS);
$self->optionTestSet(CFGOPT_REPO_S3_BUCKET, $strBucket);
$self->optionTestSet(CFGOPT_REPO_S3_ENDPOINT, $strEndpoint);
$self->optionTestSet(CFGOPT_REPO_S3_REGION, $strRegion);
$self->optionTestSet(CFGOPT_STANZA, $self->stanza());
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
################################################################################################################################
if ($self->begin('validation'))
{
if ($self->vm eq VM_U12)
{
&log(INFO, 'cannot test - certificates are no longer maintained for ' . $self->vm());
}
else
{
#-----------------------------------------------------------------------------------------------------------------------
if ($self->vm() eq VM_CO7)
{
# Tests fails on co7 because by default certs cannot be located. This logic may need to be changed in the future if
# this bug gets fixed by Red Hat. UPDATE: The behavior changed here but it does not seems to be fixed.
$self->testException(
sub {storageRepo({strStanza => 'test1'})->list('/')}, ERROR_HOST_CONNECT,
'SSL connect attempt failed with unknown error error.*certificate verify failed',
'cert verify fails on ' . VM_CO7);
# It should work when verification is disabled
$self->optionTestSetBool(CFGOPT_REPO_S3_VERIFY_TLS, false);
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
$self->testException(
sub {storageRepo({strStanza => 'test2'})->list('/')}, ERROR_PROTOCOL, 'S3 request error \[403\] Forbidden.*',
'connection succeeds with verification disabled, (expected) error on invalid access key');
$self->optionTestClear(CFGOPT_REPO_S3_VERIFY_TLS);
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
}
#-----------------------------------------------------------------------------------------------------------------------
# CO7 doesn't locate certs automatically so specify the path
if ($self->vm() eq VM_CO7)
{
$self->optionTestSet(CFGOPT_REPO_S3_CA_FILE, '/etc/pki/tls/certs/ca-bundle.crt');
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
}
$self->testException(
sub {storageRepo({strStanza => 'test3'})->list('/')}, ERROR_PROTOCOL, 'S3 request error \[403\] Forbidden.*',
'connection succeeds, (expected) error on invalid access key');
if ($self->vm() eq VM_CO7)
{
$self->optionTestClear(CFGOPT_REPO_S3_CA_FILE);
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
}
#-----------------------------------------------------------------------------------------------------------------------
$self->optionTestSet(CFGOPT_REPO_S3_CA_PATH, '/bogus');
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
$self->testException(
sub {storageRepo({strStanza => 'test4'})->list('/')}, ERROR_HOST_CONNECT,
$self->vm() eq VM_CO6 ? 'SSL connect attempt failed with unknown error.*certificate verify failed' : 'No such file or directory',
'invalid ca path');
}
}
}
1;

View File

@ -1,219 +0,0 @@
####################################################################################################################################
# S3 Storage Tests
####################################################################################################################################
package pgBackRestTest::Module::Storage::StorageS3PerlTest;
use parent 'pgBackRestTest::Env::S3EnvTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::LibC qw(:crypto);
use pgBackRest::Storage::S3::Driver;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# initTest
####################################################################################################################################
sub initTest
{
my $self = shift;
executeTest("$self->{strS3Command} rm --recursive s3://pgbackrest-dev");
}
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Initialize the driver
my $oS3 = $self->initS3();
my $oStorage = new pgBackRest::Storage::Local('', $oS3);
# Test variables
my $strFile = 'file.txt';
my $strFileContent = 'TESTDATA';
my $iFileLength = length($strFileContent);
################################################################################################################################
if ($self->begin('exists()'))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oStorage->exists($strFile)}, false, 'root file does not exist');
#---------------------------------------------------------------------------------------------------------------------------
storageTest()->put($strFile, $strFileContent);
executeTest("$self->{strS3Command} cp " . $self->testPath() . "/${strFile} s3://pgbackrest-dev");
$self->testResult(sub {$oStorage->exists($strFile)}, true, 'root file exists');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oStorage->pathExists('/path/to')}, false, 'sub path does not exist');
$self->testResult(sub {$oStorage->exists("/path/to/${strFile}")}, false, 'sub file does not exist');
#---------------------------------------------------------------------------------------------------------------------------
executeTest("$self->{strS3Command} cp " . $self->testPath() . "/${strFile} s3://pgbackrest-dev/path/to/${strFile}");
$self->testResult(sub {$oStorage->pathExists('/path/to')}, true, 'sub path exists');
# $oStorage->pathExists('/path/to');
$self->testResult(sub {$oStorage->exists("/path/to/${strFile}")}, true, 'sub file exists');
}
################################################################################################################################
if ($self->begin('manifest()'))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oStorage->manifest('')}, '{. => {type => d}}', 'no files');
#---------------------------------------------------------------------------------------------------------------------------
storageTest()->put($strFile, $strFileContent);
storageTest()->put("${strFile}2", $strFileContent . '2');
executeTest("$self->{strS3Command} cp " . $self->testPath() . "/${strFile} s3://pgbackrest-dev");
executeTest("$self->{strS3Command} cp " . $self->testPath() . "/${strFile}2 s3://pgbackrest-dev/path/to/${strFile}2");
$self->testResult(
sub {$oStorage->manifest('')},
'{. => {type => d}, file.txt => {size => 8, type => f}, path => {type => d}, path/to => {type => d},' .
' path/to/file.txt2 => {size => 9, type => f}}',
'root path');
$self->testResult(
sub {$oStorage->manifest('/path/to')}, '{. => {type => d}, file.txt2 => {size => 9, type => f}}', 'sub path');
}
################################################################################################################################
if ($self->begin('list()'))
{
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(
sub {$oStorage->list('')}, '[undef]', 'no files');
#---------------------------------------------------------------------------------------------------------------------------
storageTest()->put($strFile, $strFileContent);
storageTest()->put("${strFile}2", $strFileContent . '2');
executeTest("$self->{strS3Command} cp " . $self->testPath() . "/${strFile} s3://pgbackrest-dev");
executeTest("$self->{strS3Command} cp " . $self->testPath() . "/${strFile}2 s3://pgbackrest-dev/path/to/${strFile}2");
$self->testResult(sub {$oStorage->list('')}, '(file.txt, path)', 'root path');
$self->testResult(sub {$oStorage->list('/path/to')}, 'file.txt2', 'sub path');
}
################################################################################################################################
if ($self->begin('remove()'))
{
#---------------------------------------------------------------------------------------------------------------------------
$oStorage->put($strFile, $strFileContent);
$oStorage->put("/path/to/${strFile}2", $strFileContent);
$oStorage->put("/path/to/${strFile}3", $strFileContent);
$oStorage->put("/path/to/${strFile}4 \@+", $strFileContent);
$self->testResult(
sub {$oStorage->manifest('/')},
'{. => {type => d}, file.txt => {size => 8, type => f}, path => {type => d}, path/to => {type => d},' .
' path/to/file.txt2 => {size => 8, type => f}, path/to/file.txt3 => {size => 8, type => f},' .
' path/to/file.txt4 @+ => {size => 8, type => f}}',
'check manifest');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oStorage->remove('/path/to', {bRecurse => true})}, true, 'remove subpath');
$self->testResult(
sub {$oStorage->manifest('/')},
'{. => {type => d}, file.txt => {size => 8, type => f}}', 'check manifest');
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oStorage->remove($strFile)}, true, 'remove file');
$self->testResult(sub {$oStorage->manifest('/')}, '{. => {type => d}}', 'check manifest');
}
################################################################################################################################
if ($self->begin('info()'))
{
#---------------------------------------------------------------------------------------------------------------------------
storageTest()->put($strFile, $strFileContent);
storageTest()->put("${strFile}2", $strFileContent . '2');
executeTest("$self->{strS3Command} cp " . $self->testPath() . "/${strFile} s3://pgbackrest-dev");
executeTest("$self->{strS3Command} cp " . $self->testPath() . "/${strFile}2 s3://pgbackrest-dev");
executeTest("$self->{strS3Command} cp " . $self->testPath() . "/${strFile}2 s3://pgbackrest-dev/path/to/${strFile}2");
$self->testResult(sub {$oStorage->info($strFile)->size()}, 8, 'file size');
$self->testResult(sub {$oStorage->info("/path/to/${strFile}2")->size()}, 9, 'file 2 size');
}
################################################################################################################################
if ($self->begin('openRead() && S3::FileRead'))
{
# Create a random 1mb file
my $strRandomFile = $self->testPath() . '/random@1mb.bin';
executeTest("dd if=/dev/urandom of=${strRandomFile} bs=1024k count=1", {bSuppressStdErr => true});
my $strRandom = ${storageTest()->get($strRandomFile)};
executeTest("$self->{strS3Command} cp ${strRandomFile} s3://pgbackrest-dev/path/to/${strFile}");
#---------------------------------------------------------------------------------------------------------------------------
my $tBuffer;
my $oFileRead = $self->testResult(sub {$oS3->openRead("/path/to/${strFile}")}, '[object]', 'open read');
$self->testResult(sub {$oFileRead->read(\$tBuffer, 524288)}, 524288, ' read half');
$self->testResult(sub {$oFileRead->read(\$tBuffer, 524288)}, 524288, ' read half');
$self->testResult(sub {$oFileRead->read(\$tBuffer, 512)}, 0, ' read 0');
$self->testResult(length($tBuffer), 1048576, ' check length');
$self->testResult(cryptoHashOne('sha1', $tBuffer), cryptoHashOne('sha1', $strRandom), ' check hash');
}
################################################################################################################################
if ($self->begin('openWrite() && S3::FileWrite'))
{
# Create a random 1mb file
my $strRandomFile = $self->testPath() . '/random1mb.bin';
executeTest("dd if=/dev/urandom of=${strRandomFile} bs=1024k count=1", {bSuppressStdErr => true});
my $strRandom = ${storageTest()->get($strRandomFile)};
#---------------------------------------------------------------------------------------------------------------------------
my $oFileWrite = $self->testResult(sub {$oS3->openWrite("/path/to/${strFile}")}, '[object]', 'open write');
$self->testResult(sub {$oFileWrite->name()}, "/path/to/${strFile}", ' check filename');
$self->testResult(sub {$oFileWrite->close()}, true, ' close without writing');
#---------------------------------------------------------------------------------------------------------------------------
$oFileWrite = $self->testResult(sub {$oS3->openWrite("/path/to/${strFile}" . '.@')}, '[object]', 'open write');
$self->testResult(sub {$oFileWrite->write()}, 0, ' write undef');
$self->testResult(sub {$oFileWrite->write(\$strFileContent)}, $iFileLength, ' write');
$oFileWrite->close();
$self->testResult(sub {$oS3->exists("/path/to/${strFile}" . '.@')}, true, 'destination file exists');
# Test that a premature destroy (from error or otherwise) does not rename the file
#---------------------------------------------------------------------------------------------------------------------------
$oFileWrite = $self->testResult(sub {$oS3->openWrite("/path/to/abort.file" . '.@')}, '[object]', 'open write');
$self->testResult(sub {$oFileWrite->write()}, 0, ' write undef');
$self->testResult(sub {$oFileWrite->write(\$strFileContent)}, $iFileLength, ' write');
undef($oFileWrite);
$self->testResult(sub {$oS3->exists("/path/to/abort.file")}, false, 'destination file does not exist');
#---------------------------------------------------------------------------------------------------------------------------
$oFileWrite = $self->testResult(sub {$oS3->openWrite("/path/to/${strFile}")}, '[object]', 'open write');
for (my $iIndex = 1; $iIndex <= 17; $iIndex++)
{
$self->testResult(sub {$oFileWrite->write(\$strRandom)}, 1024 * 1024, ' write 1mb');
}
$self->testResult(sub {$oFileWrite->close()}, true, ' close');
}
}
1;

View File

@ -1,184 +0,0 @@
####################################################################################################################################
# S3 Request Tests
####################################################################################################################################
package pgBackRestTest::Module::Storage::StorageS3RequestPerlTest;
use parent 'pgBackRestTest::Common::RunTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use IO::Socket::SSL;
use POSIX qw(strftime);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Http::Client;
use pgBackRest::Common::Log;
use pgBackRest::Common::Wait;
use pgBackRest::Storage::S3::Request;
use pgBackRestTest::Common::ContainerTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# Port to use for testing
####################################################################################################################################
use constant HTTPS_TEST_PORT => 9443;
####################################################################################################################################
# httpsServerResponse
####################################################################################################################################
sub httpsServerResponse
{
my $self = shift;
my $iResponseCode = shift;
my $strContent = shift;
# Write header
$self->{oConnection}->write("HTTP/1.1 ${iResponseCode} GenericMessage\r\n");
$self->{oConnection}->write(HTTP_HEADER_CONTENT_LENGTH . ': ' . (defined($strContent) ? length($strContent) : 0) . "\r\n");
# Write new line before content (even if there isn't any)
$self->{oConnection}->write("\r\n");
# Write content
if (defined($strContent))
{
$self->{oConnection}->write($strContent);
}
# This will block until the connection is closed by the client
$self->{oConnection}->read();
}
####################################################################################################################################
# httpsServerAccept
####################################################################################################################################
sub httpsServerAccept
{
my $self = shift;
# Wait for a connection
$self->{oConnection} = $self->{oSocketServer}->accept()
or confess "failed to accept or handshake $!, $SSL_ERROR";
&log(INFO, " * socket server connected");
}
####################################################################################################################################
# httpsServer
####################################################################################################################################
sub httpsServer
{
my $self = shift;
my $fnServer = shift;
# Fork off the server
if (fork() == 0)
{
# Run server function
$fnServer->();
exit 0;
}
}
####################################################################################################################################
# Start the https testing server
####################################################################################################################################
sub initModule
{
my $self = shift;
# Open the domain socket
$self->{oSocketServer} = IO::Socket::SSL->new(
LocalAddr => '127.0.0.1', LocalPort => HTTPS_TEST_PORT, Listen => 1, SSL_cert_file => CERT_FAKE_SERVER,
SSL_key_file => CERT_FAKE_SERVER_KEY)
or confess "unable to open https server for testing: $!";
&log(INFO, " * socket server open");
}
####################################################################################################################################
# Stop the https testing server
####################################################################################################################################
sub cleanModule
{
my $self = shift;
# Shutdown server
$self->{oSocketServer}->close();
&log(INFO, " * socket server closed");
}
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Initialize request object
my $oS3Request = new pgBackRest::Storage::S3::Request(
BOGUS, BOGUS, BOGUS, BOGUS, BOGUS, {strHost => '127.0.0.1', iPort => HTTPS_TEST_PORT, bVerifySsl => false});
################################################################################################################################
if ($self->begin('success'))
{
$self->httpsServer(sub
{
$self->httpsServerAccept();
$self->httpsServerResponse(200);
#-----------------------------------------------------------------------------------------------------------------------
$self->httpsServerAccept();
$self->httpsServerResponse(200);
});
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oS3Request->request(HTTP_VERB_GET)}, undef, 'successful request');
$self->testResult(sub {$oS3Request->request(HTTP_VERB_GET)}, undef, 'successful request');
}
################################################################################################################################
if ($self->begin('retry'))
{
$self->httpsServer(sub
{
$self->httpsServerAccept();
$self->httpsServerResponse(500);
$self->httpsServerAccept();
$self->httpsServerResponse(500);
$self->httpsServerAccept();
$self->httpsServerResponse(200);
#-----------------------------------------------------------------------------------------------------------------------
$self->httpsServerAccept();
$self->httpsServerResponse(500);
$self->httpsServerAccept();
$self->httpsServerResponse(500);
$self->httpsServerAccept();
$self->httpsServerResponse(500);
$self->httpsServerAccept();
$self->httpsServerResponse(500);
$self->httpsServerAccept();
$self->httpsServerResponse(500);
});
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oS3Request->request(HTTP_VERB_GET)}, undef, 'successful request after retries');
$self->testException(
sub {$oS3Request->request(HTTP_VERB_GET)}, ERROR_PROTOCOL, 'S3 request error after 5 tries \[500\] GenericMessage.*');
}
}
1;

View File

@ -465,6 +465,11 @@ testRun(void)
storagePathNP(storageTest, strNew("/path/toot")), AssertError,
"absolute path '/path/toot' is not in base path '/path/to'");
// Path enforcement disabled
storagePathEnforceSet(storageTest, false);
TEST_RESULT_STR(strPtr(storagePathNP(storageTest, strNew("/bogus"))), "/bogus", "path enforce disabled");
storagePathEnforceSet(storageTest, true);
TEST_ERROR(storagePathNP(storageTest, strNew("<TEST")), AssertError, "end > not found in path expression '<TEST'");
TEST_ERROR(
storagePathNP(storageTest, strNew("<TEST>" BOGUS_STR)), AssertError,