1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-01-18 04:58:51 +02:00

The local command for restore is implemented entirely in C.

This is just the part of restore run by the local helper processes, not the entire command.

Even so, various optimizations in the code (like pipelining and optimizations for zero-length files) should make the restore command faster on object stores.
This commit is contained in:
David Steele 2019-05-20 17:07:37 -04:00
parent a839830333
commit 1bc84c6474
15 changed files with 597 additions and 290 deletions

View File

@ -14,6 +14,12 @@
<release-list>
<release date="XXXX-XX-XX" version="2.15dev" title="UNDER DEVELOPMENT">
<release-core-list>
<release-improvement-list>
<release-item>
<p>The <cmd>local</cmd> command for restore is implemented entirely in C.</p>
</release-item>
</release-improvement-list>
<release-development-list>
<release-item>
<release-item-contributor-list>

View File

@ -172,7 +172,8 @@ sub outputRead
# Raise the error if a warning is not requested
if (!$bWarnOnError)
{
confess &log(ERROR, $strError, $hResult->{err}, $bSuppressLog);
confess &log(
ERROR, $strError . (defined($hResult->{errStack}) ? "\n$hResult->{errStack}" : ''), $hResult->{err}, $bSuppressLog);
}
&log(WARN, $strError, $hResult->{err});

View File

@ -54,7 +54,6 @@ sub init
my $hCommandMap =
{
&OP_BACKUP_FILE => sub {backupFile(@{shift()})},
&OP_RESTORE_FILE => sub {restoreFile(@{shift()})},
# To be run after each command to keep the remote alive
&OP_POST => sub {protocolKeepAlive()},

View File

@ -330,6 +330,15 @@ sub process
eval
{
$hJob->{rResult} = $hLocal->{oLocal}->outputRead(true, undef, undef, true);
# Create a result array when the result is not already an array. The Perl locals always return an array but the C
# locals only do so when needed.
if (ref($hJob->{rResult}) ne 'ARRAY')
{
my @resultArray = (${$hJob->{rResult}});
$hJob->{rResult} = \@resultArray;
}
return true;
}
or do

View File

@ -25,156 +25,6 @@ use pgBackRest::Storage::Filter::Gzip;
use pgBackRest::Storage::Filter::Sha;
use pgBackRest::Storage::Helper;
####################################################################################################################################
# restoreFile
#
# Restores a single file.
####################################################################################################################################
sub restoreFile
{
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strDbFile,
$lSize,
$lModificationTime,
$strChecksum,
$bZero,
$bForce, # Force flag
$strRepoFile,
$strReference,
$strMode,
$strUser,
$strGroup,
$lCopyTimeStart, # Backup start time - used for size/timestamp deltas
$bDelta, # Is restore a delta?
$strBackupPath, # Backup path
$bSourceCompressed, # Is the source compressed?
$strCipherPass, # Passphrase to decrypt the repo file (undefined if repo not encrypted)
) =
logDebugParam
(
__PACKAGE__ . '::restoreFile', \@_,
{name => 'strDbFile', trace => true},
{name => 'lSize', trace => true},
{name => 'lModificationTime', trace => true},
{name => 'strChecksum', required => false, trace => true},
{name => 'bZero', required => false, default => false, trace => true},
{name => 'bForce', trace => true},
{name => 'strRepoFile', trace => true},
{name => 'strReference', required => false, trace => true},
{name => 'strMode', trace => true},
{name => 'strUser', trace => true},
{name => 'strGroup', trace => true},
{name => 'lCopyTimeStart', trace => true},
{name => 'bDelta', trace => true},
{name => 'strBackupPath', trace => true},
{name => 'bSourceCompressed', trace => true},
{name => 'strCipherPass', required => false, trace => true},
);
# Does the file need to be copied?
my $oStorageDb = storageDb();
my $bCopy = true;
# Zero file if requested
if ($bZero)
{
$bCopy = false;
my $oDestinationFileIo = $oStorageDb->openWrite(
$strDbFile, {strMode => $strMode, strUser => $strUser, strGroup => $strGroup, lTimestamp => $lModificationTime});
$oDestinationFileIo->open();
# Now truncate to the original size. This will create a sparse file which is very efficient for this use case.
truncate($oDestinationFileIo->handle(), $lSize);
$oDestinationFileIo->close();
}
# Perform delta if requested
elsif ($bDelta)
{
my $oStat = $oStorageDb->info($strDbFile, {bIgnoreMissing => true});
# Do the delta if the file exists and is not a link or the link destination exists
if (defined($oStat) &&
(!S_ISLNK($oStat->mode) ||
$oStorageDb->exists(
$oStorageDb->pathAbsolute(dirname($strDbFile), $oStorageDb->{oDriver}->linkDestination($strDbFile)))))
{
# If force then use size/timestamp delta
if ($bForce)
{
# Make sure that timestamp/size are equal and that timestamp is before the copy start time of the backup
if (defined($oStat) && $oStat->size == $lSize &&
$oStat->mtime == $lModificationTime && $oStat->mtime < $lCopyTimeStart)
{
$bCopy = false;
}
}
else
{
my ($strActualChecksum, $lActualSize) = $oStorageDb->hashSize($strDbFile);
if ($lActualSize == $lSize && ($lSize == 0 || $strActualChecksum eq $strChecksum))
{
# Even if hash is the same set the time back to backup time. This helps with unit testing, but also
# presents a pristine version of the database after restore.
utime($lModificationTime, $lModificationTime, $strDbFile)
or confess &log(ERROR, "unable to set time for ${strDbFile}");
$bCopy = false;
}
}
}
}
# Copy file from repository to database
if ($bCopy)
{
# Add sha filter
my $rhyFilter = [{strClass => STORAGE_FILTER_SHA}];
# Add compression
if ($bSourceCompressed)
{
unshift(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP, rxyParam => [{strCompressType => STORAGE_DECOMPRESS}]});
}
# Open destination file
my $oDestinationFileIo = $oStorageDb->openWrite(
$strDbFile,
{strMode => $strMode, strUser => $strUser, strGroup => $strGroup, lTimestamp => $lModificationTime,
rhyFilter => $rhyFilter});
# Copy file
storageRepo()->copy(
storageRepo()->openRead(
STORAGE_REPO_BACKUP . qw(/) . (defined($strReference) ? $strReference : $strBackupPath) .
"/${strRepoFile}" . ($bSourceCompressed ? qw{.} . COMPRESS_EXT : ''),
{bProtocolCompress => !$bSourceCompressed && $lSize != 0, strCipherPass => $strCipherPass}),
$oDestinationFileIo);
# Validate checksum
if ($oDestinationFileIo->result(COMMON_IO_HANDLE) != 0 && $oDestinationFileIo->result(STORAGE_FILTER_SHA) ne $strChecksum)
{
confess &log(ERROR,
"error restoring ${strDbFile}: actual checksum '" . $oDestinationFileIo->digest() .
"' does not match expected checksum ${strChecksum}", ERROR_CHECKSUM);
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bCopy', value => $bCopy, trace => true}
);
}
push @EXPORT, qw(restoreFile);
####################################################################################################################################
# restoreLog
#

View File

@ -55,6 +55,8 @@ SRCS = \
command/command.c \
command/control/control.c \
command/local/local.c \
command/restore/file.c \
command/restore/protocol.c \
command/remote/remote.c \
common/compress/gzip/common.c \
common/compress/gzip/compress.c \
@ -223,12 +225,18 @@ command/help/help.o: command/help/help.c build.auto.h common/assert.h common/deb
command/info/info.o: command/info/info.c build.auto.h command/archive/common.h command/info/info.h common/assert.h common/crypto/common.h common/debug.h common/error.auto.h common/error.h common/ini.h common/io/filter/filter.h common/io/filter/group.h common/io/handleWrite.h common/io/read.h common/io/write.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 info/info.h info/infoArchive.h info/infoBackup.h info/infoPg.h perl/exec.h postgres/interface.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h
$(CC) $(CFLAGS) $(CMAKE) -c command/info/info.c -o command/info/info.o
command/local/local.o: command/local/local.c build.auto.h command/archive/get/protocol.h command/archive/push/protocol.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/handleRead.h common/io/handleWrite.h common/io/read.h common/io/write.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/protocol.h protocol/client.h protocol/command.h protocol/helper.h protocol/server.h
command/local/local.o: command/local/local.c build.auto.h command/archive/get/protocol.h command/archive/push/protocol.h command/restore/protocol.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/handleRead.h common/io/handleWrite.h common/io/read.h common/io/write.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/protocol.h protocol/client.h protocol/command.h protocol/helper.h protocol/server.h
$(CC) $(CFLAGS) $(CMAKE) -c command/local/local.c -o command/local/local.o
command/remote/remote.o: command/remote/remote.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/handleRead.h common/io/handleWrite.h common/io/read.h common/io/write.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/protocol.h protocol/client.h protocol/command.h protocol/helper.h protocol/server.h storage/remote/protocol.h
$(CC) $(CFLAGS) $(CMAKE) -c command/remote/remote.c -o command/remote/remote.o
command/restore/file.o: command/restore/file.c build.auto.h command/restore/file.h common/assert.h common/compress/gzip/common.h common/compress/gzip/decompress.h common/crypto/cipherBlock.h common/crypto/common.h common/crypto/hash.h common/debug.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/write.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 storage/helper.h storage/info.h storage/posix/common.h storage/read.h storage/storage.h storage/write.h
$(CC) $(CFLAGS) $(CMAKE) -c command/restore/file.c -o command/restore/file.o
command/restore/protocol.o: command/restore/protocol.c build.auto.h command/restore/file.h command/restore/protocol.h common/assert.h common/crypto/common.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/io.h common/io/read.h common/io/write.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 protocol/server.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h
$(CC) $(CFLAGS) $(CMAKE) -c command/restore/protocol.c -o command/restore/protocol.o
common/compress/gzip/common.o: common/compress/gzip/common.c build.auto.h common/assert.h common/compress/gzip/common.h common/debug.h common/error.auto.h common/error.h common/logLevel.h common/memContext.h common/stackTrace.h common/type/convert.h
$(CC) $(CFLAGS) $(CMAKE) -c common/compress/gzip/common.c -o common/compress/gzip/common.o

View File

@ -5,6 +5,7 @@ Local Command
#include "command/archive/get/protocol.h"
#include "command/archive/push/protocol.h"
#include "command/restore/protocol.h"
#include "common/debug.h"
#include "common/io/handleRead.h"
#include "common/io/handleWrite.h"
@ -33,6 +34,7 @@ cmdLocal(int handleRead, int handleWrite)
ProtocolServer *server = protocolServerNew(name, PROTOCOL_SERVICE_LOCAL_STR, read, write);
protocolServerHandlerAdd(server, archiveGetProtocol);
protocolServerHandlerAdd(server, archivePushProtocol);
protocolServerHandlerAdd(server, restoreProtocol);
protocolServerProcess(server);
}
MEM_CONTEXT_TEMP_END();

198
src/command/restore/file.c Normal file
View File

@ -0,0 +1,198 @@
/***********************************************************************************************************************************
Restore File
***********************************************************************************************************************************/
#include "build.auto.h"
#include <fcntl.h>
#include <unistd.h>
#include <utime.h>
#include "command/restore/file.h"
#include "common/compress/gzip/common.h"
#include "common/compress/gzip/decompress.h"
#include "common/crypto/cipherBlock.h"
#include "common/crypto/hash.h"
#include "common/debug.h"
#include "common/io/filter/group.h"
#include "common/io/filter/size.h"
#include "common/io/io.h"
#include "common/log.h"
#include "config/config.h"
#include "storage/posix/common.h"
#include "storage/helper.h"
/***********************************************************************************************************************************
Copy a file from the backup to the specified destination
***********************************************************************************************************************************/
bool
restoreFile(
const String *repoFile, const String *repoFileReference, bool repoFileCompressed, const String *pgFile,
const String *pgFileChecksum, bool pgFileZero, uint64_t pgFileSize, time_t pgFileModified, mode_t pgFileMode,
const String *pgFileUser, const String *pgFileGroup, time_t copyTimeBegin, bool delta, bool deltaForce,
const String *cipherPass)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, repoFile);
FUNCTION_LOG_PARAM(STRING, repoFileReference);
FUNCTION_LOG_PARAM(BOOL, repoFileCompressed);
FUNCTION_LOG_PARAM(STRING, pgFile);
FUNCTION_LOG_PARAM(STRING, pgFileChecksum);
FUNCTION_LOG_PARAM(BOOL, pgFileZero);
FUNCTION_LOG_PARAM(UINT64, pgFileSize);
FUNCTION_LOG_PARAM(INT64, pgFileModified);
FUNCTION_LOG_PARAM(MODE, pgFileMode);
FUNCTION_LOG_PARAM(STRING, pgFileUser);
FUNCTION_LOG_PARAM(STRING, pgFileGroup);
FUNCTION_LOG_PARAM(INT64, copyTimeBegin);
FUNCTION_LOG_PARAM(BOOL, delta);
FUNCTION_LOG_PARAM(BOOL, deltaForce);
FUNCTION_TEST_PARAM(STRING, cipherPass);
FUNCTION_LOG_END();
ASSERT(repoFile != NULL);
ASSERT(repoFileReference != NULL);
ASSERT(pgFile != NULL);
// Was the file copied?
bool result = true;
// Create destination file. We may not use this but it makes sense to only create it in one place if we do.
MEM_CONTEXT_TEMP_BEGIN()
{
// Perform delta if requested. Delta zero-length files to avoid overwriting the file if the timestamp is correct.
if (delta && !pgFileZero)
{
// Perform delta if the file exists
StorageInfo info = storageInfoP(storagePg(), pgFile, .ignoreMissing = true, .followLink = true);
if (info.exists)
{
// If force then use size/timestamp delta
if (deltaForce)
{
// Make sure that timestamp/size are equal and that timestamp is before the copy start time of the backup
if (info.size == pgFileSize && info.timeModified == pgFileModified && info.timeModified < copyTimeBegin)
result = false;
}
// Else use size and checksum
else
{
// Only continue delta if the file size is as expected
if (info.size == pgFileSize)
{
// Generate checksum for the file if size is not zero
IoFilterGroup *filterGroup = ioFilterGroupNew();
if (info.size != 0)
{
IoRead *read = storageReadIo(storageNewReadNP(storagePgWrite(), pgFile));
ioFilterGroupAdd(filterGroup, cryptoHashNew(HASH_TYPE_SHA1_STR));
ioReadFilterGroupSet(read, filterGroup);
Buffer *buffer = bufNew(ioBufferSize());
ioReadOpen(read);
do
{
ioRead(read, buffer);
bufUsedZero(buffer);
}
while (!ioReadEof(read));
ioReadClose(read);
}
// If size and checksum are equal then no need to copy the file
if (pgFileSize == 0 ||
strEq(pgFileChecksum, varStr(ioFilterGroupResult(filterGroup, CRYPTO_HASH_FILTER_TYPE_STR))))
{
// Even if hash/size are the same set the time back to backup time. This helps with unit testing, but
// also presents a pristine version of the database after restore.
if (info.timeModified != pgFileModified)
{
THROW_ON_SYS_ERROR_FMT(
utime(
strPtr(storagePath(storagePg(), pgFile)),
&((struct utimbuf){.actime = pgFileModified, .modtime = pgFileModified})) == -1,
FileInfoError, "unable to set time for '%s'", strPtr(storagePath(storagePg(), pgFile)));
}
result = false;
}
}
}
}
}
// Copy file from repository to database or create zero-length/sparse file
if (result)
{
// Create destination file
StorageWrite *pgFileWrite = storageNewWriteP(
storagePgWrite(), pgFile, .modeFile = pgFileMode, .user = pgFileUser, .group = pgFileGroup,
.timeModified = pgFileModified, .noAtomic = true, .noCreatePath = true, .noSyncPath = true);
// If size is zero/sparse no need to actually copy
if (pgFileSize == 0 || pgFileZero)
{
ioWriteOpen(storageWriteIo(pgFileWrite));
// Truncate the file to specified length (note in this case the file with grow, not shrink)
if (pgFileZero)
{
THROW_ON_SYS_ERROR_FMT(
ftruncate(ioWriteHandle(storageWriteIo(pgFileWrite)), (off_t)pgFileSize) == -1, FileWriteError,
"unable to truncate '%s'", strPtr(pgFile));
// Report the file as not copied
result = false;
}
ioWriteClose(storageWriteIo(pgFileWrite));
}
// Else perform the copy
else
{
IoFilterGroup *filterGroup = ioFilterGroupNew();
// Add decryption filter
if (cipherPass != NULL)
ioFilterGroupAdd(filterGroup, cipherBlockNew(cipherModeDecrypt, cipherTypeAes256Cbc, BUFSTR(cipherPass), NULL));
// Add decompression filter
if (repoFileCompressed)
ioFilterGroupAdd(filterGroup, gzipDecompressNew(false));
// Add sha1 filter
ioFilterGroupAdd(filterGroup, cryptoHashNew(HASH_TYPE_SHA1_STR));
// Add size filter
ioFilterGroupAdd(filterGroup, ioSizeNew());
ioWriteFilterGroupSet(storageWriteIo(pgFileWrite), filterGroup);
// Copy file
storageCopyNP(
storageNewReadNP(
storageRepo(),
strNewFmt(
STORAGE_REPO_BACKUP "/%s/%s%s", strPtr(repoFileReference), strPtr(repoFile),
repoFileCompressed ? "." GZIP_EXT : "")),
pgFileWrite);
// Validate checksum
if (!strEq(pgFileChecksum, varStr(ioFilterGroupResult(filterGroup, CRYPTO_HASH_FILTER_TYPE_STR))))
{
THROW_FMT(
ChecksumError,
"error restoring '%s': actual checksum '%s' does not match expected checksum '%s'", strPtr(pgFile),
strPtr(varStr(ioFilterGroupResult(filterGroup, CRYPTO_HASH_FILTER_TYPE_STR))), strPtr(pgFileChecksum));
}
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(BOOL, result);
}

View File

@ -0,0 +1,20 @@
/***********************************************************************************************************************************
Restore File
***********************************************************************************************************************************/
#ifndef COMMAND_RESTORE_FILE_H
#define COMMAND_RESTORE_FILE_H
#include "common/crypto/common.h"
#include "common/type/string.h"
#include "storage/storage.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
bool restoreFile(
const String *repoFile, const String *repoFileReference, bool repoFileCompressed, const String *pgFile,
const String *pgFileChecksum, bool pgFileZero, uint64_t pgFileSize, time_t pgFileModified, mode_t pgFileMode,
const String *pgFileUser, const String *pgFileGroup, time_t copyTimeBegin, bool delta, bool deltaForce,
const String *cipherPass);
#endif

View File

@ -0,0 +1,63 @@
/***********************************************************************************************************************************
Restore Protocol Handler
***********************************************************************************************************************************/
#include "build.auto.h"
#include "command/restore/file.h"
#include "command/restore/protocol.h"
#include "common/debug.h"
#include "common/io/io.h"
#include "common/log.h"
#include "common/memContext.h"
#include "config/config.h"
#include "storage/helper.h"
/***********************************************************************************************************************************
Constants
***********************************************************************************************************************************/
STRING_EXTERN(PROTOCOL_COMMAND_RESTORE_FILE_STR, PROTOCOL_COMMAND_RESTORE_FILE);
/***********************************************************************************************************************************
Process protocol requests
***********************************************************************************************************************************/
bool
restoreProtocol(const String *command, const VariantList *paramList, ProtocolServer *server)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, command);
FUNCTION_LOG_PARAM(VARIANT_LIST, paramList);
FUNCTION_LOG_PARAM(PROTOCOL_SERVER, server);
FUNCTION_LOG_END();
ASSERT(command != NULL);
// Get the repo storage in case it is remote and encryption settings need to be pulled down
storageRepo();
// Attempt to satisfy the request -- we may get requests that are meant for other handlers
bool found = true;
MEM_CONTEXT_TEMP_BEGIN()
{
if (strEq(command, PROTOCOL_COMMAND_RESTORE_FILE_STR))
{
protocolServerResponse(
server,
VARBOOL(
restoreFile(varStr(varLstGet(paramList, 6)),
varLstGet(paramList, 7) ? varStr(varLstGet(paramList, 7)) : varStr(varLstGet(paramList, 13)),
varBoolForce(varLstGet(paramList, 14)), varStr(varLstGet(paramList, 0)), varStr(varLstGet(paramList, 3)),
varBoolForce(varLstGet(paramList, 4)), varUInt64(varLstGet(paramList, 1)),
(time_t)varInt64Force(varLstGet(paramList, 2)), cvtZToUIntBase(strPtr(varStr(varLstGet(paramList, 8))), 8),
varStr(varLstGet(paramList, 9)), varStr(varLstGet(paramList, 10)),
(time_t)varInt64Force(varLstGet(paramList, 11)), varBoolForce(varLstGet(paramList, 12)),
varBoolForce(varLstGet(paramList, 5)),
varLstSize(paramList) == 16 ? varStr(varLstGet(paramList, 15)) : NULL)));
}
else
found = false;
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(BOOL, found);
}

View File

@ -0,0 +1,22 @@
/***********************************************************************************************************************************
Restore Protocol Handler
***********************************************************************************************************************************/
#ifndef COMMAND_RESTORE_PROTOCOL_H
#define COMMAND_RESTORE_PROTOCOL_H
#include "common/type/string.h"
#include "common/type/variantList.h"
#include "protocol/server.h"
/***********************************************************************************************************************************
Constants
***********************************************************************************************************************************/
#define PROTOCOL_COMMAND_RESTORE_FILE "restoreFile"
STRING_DECLARE(PROTOCOL_COMMAND_RESTORE_FILE_STR);
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
bool restoreProtocol(const String *command, const VariantList *paramList, ProtocolServer *server);
#endif

View File

@ -155,7 +155,8 @@ main(int argListSize, const char *argList[])
case cfgCmdLocal:
{
if (strEq(cfgOptionStr(cfgOptCommand), CFGCMD_ARCHIVE_GET_ASYNC_STR) ||
strEq(cfgOptionStr(cfgOptCommand), CFGCMD_ARCHIVE_PUSH_ASYNC_STR))
strEq(cfgOptionStr(cfgOptCommand), CFGCMD_ARCHIVE_PUSH_ASYNC_STR) ||
strEq(cfgOptionStr(cfgOptCommand), CFGCMD_RESTORE_STR))
{
cmdLocal(STDIN_FILENO, STDOUT_FILENO);
}

View File

@ -11380,7 +11380,8 @@ static const EmbeddedModule embeddedModule[] =
"\n\n"
"if (!$bWarnOnError)\n"
"{\n"
"confess &log(ERROR, $strError, $hResult->{err}, $bSuppressLog);\n"
"confess &log(\n"
"ERROR, $strError . (defined($hResult->{errStack}) ? \"\\n$hResult->{errStack}\" : ''), $hResult->{err}, $bSuppressLog);\n"
"}\n"
"\n"
"&log(WARN, $strError, $hResult->{err});\n"
@ -12384,7 +12385,6 @@ static const EmbeddedModule embeddedModule[] =
"my $hCommandMap =\n"
"{\n"
"&OP_BACKUP_FILE => sub {backupFile(@{shift()})},\n"
"&OP_RESTORE_FILE => sub {restoreFile(@{shift()})},\n"
"\n\n"
"&OP_POST => sub {protocolKeepAlive()},\n"
"};\n"
@ -12673,6 +12673,13 @@ static const EmbeddedModule embeddedModule[] =
"eval\n"
"{\n"
"$hJob->{rResult} = $hLocal->{oLocal}->outputRead(true, undef, undef, true);\n"
"\n\n\n"
"if (ref($hJob->{rResult}) ne 'ARRAY')\n"
"{\n"
"my @resultArray = (${$hJob->{rResult}});\n"
"$hJob->{rResult} = \\@resultArray;\n"
"}\n"
"\n"
"return true;\n"
"}\n"
"or do\n"
@ -15066,140 +15073,6 @@ static const EmbeddedModule embeddedModule[] =
"use pgBackRest::Storage::Filter::Sha;\n"
"use pgBackRest::Storage::Helper;\n"
"\n\n\n\n\n\n"
"sub restoreFile\n"
"{\n"
"\n"
"my\n"
"(\n"
"$strOperation,\n"
"$strDbFile,\n"
"$lSize,\n"
"$lModificationTime,\n"
"$strChecksum,\n"
"$bZero,\n"
"$bForce,\n"
"$strRepoFile,\n"
"$strReference,\n"
"$strMode,\n"
"$strUser,\n"
"$strGroup,\n"
"$lCopyTimeStart,\n"
"$bDelta,\n"
"$strBackupPath,\n"
"$bSourceCompressed,\n"
"$strCipherPass,\n"
") =\n"
"logDebugParam\n"
"(\n"
"__PACKAGE__ . '::restoreFile', \\@_,\n"
"{name => 'strDbFile', trace => true},\n"
"{name => 'lSize', trace => true},\n"
"{name => 'lModificationTime', trace => true},\n"
"{name => 'strChecksum', required => false, trace => true},\n"
"{name => 'bZero', required => false, default => false, trace => true},\n"
"{name => 'bForce', trace => true},\n"
"{name => 'strRepoFile', trace => true},\n"
"{name => 'strReference', required => false, trace => true},\n"
"{name => 'strMode', trace => true},\n"
"{name => 'strUser', trace => true},\n"
"{name => 'strGroup', trace => true},\n"
"{name => 'lCopyTimeStart', trace => true},\n"
"{name => 'bDelta', trace => true},\n"
"{name => 'strBackupPath', trace => true},\n"
"{name => 'bSourceCompressed', trace => true},\n"
"{name => 'strCipherPass', required => false, trace => true},\n"
");\n"
"\n\n"
"my $oStorageDb = storageDb();\n"
"my $bCopy = true;\n"
"\n\n"
"if ($bZero)\n"
"{\n"
"$bCopy = false;\n"
"\n"
"my $oDestinationFileIo = $oStorageDb->openWrite(\n"
"$strDbFile, {strMode => $strMode, strUser => $strUser, strGroup => $strGroup, lTimestamp => $lModificationTime});\n"
"$oDestinationFileIo->open();\n"
"\n\n"
"truncate($oDestinationFileIo->handle(), $lSize);\n"
"\n"
"$oDestinationFileIo->close();\n"
"}\n"
"\n"
"elsif ($bDelta)\n"
"{\n"
"my $oStat = $oStorageDb->info($strDbFile, {bIgnoreMissing => true});\n"
"\n\n"
"if (defined($oStat) &&\n"
"(!S_ISLNK($oStat->mode) ||\n"
"$oStorageDb->exists(\n"
"$oStorageDb->pathAbsolute(dirname($strDbFile), $oStorageDb->{oDriver}->linkDestination($strDbFile)))))\n"
"{\n"
"\n"
"if ($bForce)\n"
"{\n"
"\n"
"if (defined($oStat) && $oStat->size == $lSize &&\n"
"$oStat->mtime == $lModificationTime && $oStat->mtime < $lCopyTimeStart)\n"
"{\n"
"$bCopy = false;\n"
"}\n"
"}\n"
"else\n"
"{\n"
"my ($strActualChecksum, $lActualSize) = $oStorageDb->hashSize($strDbFile);\n"
"\n"
"if ($lActualSize == $lSize && ($lSize == 0 || $strActualChecksum eq $strChecksum))\n"
"{\n"
"\n\n"
"utime($lModificationTime, $lModificationTime, $strDbFile)\n"
"or confess &log(ERROR, \"unable to set time for ${strDbFile}\");\n"
"\n"
"$bCopy = false;\n"
"}\n"
"}\n"
"}\n"
"}\n"
"\n\n"
"if ($bCopy)\n"
"{\n"
"\n"
"my $rhyFilter = [{strClass => STORAGE_FILTER_SHA}];\n"
"\n\n"
"if ($bSourceCompressed)\n"
"{\n"
"unshift(@{$rhyFilter}, {strClass => STORAGE_FILTER_GZIP, rxyParam => [{strCompressType => STORAGE_DECOMPRESS}]});\n"
"}\n"
"\n\n"
"my $oDestinationFileIo = $oStorageDb->openWrite(\n"
"$strDbFile,\n"
"{strMode => $strMode, strUser => $strUser, strGroup => $strGroup, lTimestamp => $lModificationTime,\n"
"rhyFilter => $rhyFilter});\n"
"\n\n"
"storageRepo()->copy(\n"
"storageRepo()->openRead(\n"
"STORAGE_REPO_BACKUP . qw(/) . (defined($strReference) ? $strReference : $strBackupPath) .\n"
"\"/${strRepoFile}\" . ($bSourceCompressed ? qw{.} . COMPRESS_EXT : ''),\n"
"{bProtocolCompress => !$bSourceCompressed && $lSize != 0, strCipherPass => $strCipherPass}),\n"
"$oDestinationFileIo);\n"
"\n\n"
"if ($oDestinationFileIo->result(COMMON_IO_HANDLE) != 0 && $oDestinationFileIo->result(STORAGE_FILTER_SHA) ne $strChecksum)\n"
"{\n"
"confess &log(ERROR,\n"
"\"error restoring ${strDbFile}: actual checksum '\" . $oDestinationFileIo->digest() .\n"
"\"' does not match expected checksum ${strChecksum}\", ERROR_CHECKSUM);\n"
"}\n"
"}\n"
"\n\n"
"return logDebugReturn\n"
"(\n"
"$strOperation,\n"
"{name => 'bCopy', value => $bCopy, trace => true}\n"
");\n"
"}\n"
"\n"
"push @EXPORT, qw(restoreFile);\n"
"\n\n\n\n\n\n"
"sub restoreLog\n"
"{\n"
"\n"

View File

@ -718,6 +718,14 @@ unit:
coverage:
command/remote/remote: full
# ----------------------------------------------------------------------------------------------------------------------------
- name: restore
total: 1
coverage:
command/restore/file: full
command/restore/protocol: full
# ********************************************************************************************************************************
- name: backup

View File

@ -0,0 +1,247 @@
/***********************************************************************************************************************************
Test Restore Command
***********************************************************************************************************************************/
#include "common/compress/gzip/compress.h"
#include "common/crypto/cipherBlock.h"
#include "common/io/io.h"
#include "common/io/bufferRead.h"
#include "common/io/bufferWrite.h"
#include "storage/posix/storage.h"
#include "storage/helper.h"
#include "common/harnessConfig.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
void
testRun(void)
{
FUNCTION_HARNESS_VOID();
// Start a protocol server to test the protocol directly
Buffer *serverWrite = bufNew(8192);
IoWrite *serverWriteIo = ioBufferWriteNew(serverWrite);
ioWriteOpen(serverWriteIo);
ProtocolServer *server = protocolServerNew(
strNew("test"), strNew("test"), ioBufferReadNew(bufNew(0)), serverWriteIo);
bufUsedSet(serverWrite, 0);
// *****************************************************************************************************************************
if (testBegin("restoreFile()"))
{
const String *repoFileReferenceFull = strNew("20190509F");
const String *repoFile1 = strNew("pg_data/testfile");
// Load Parameters
StringList *argList = strLstNew();
strLstAddZ(argList, "pgbackrest");
strLstAddZ(argList, "--stanza=test1");
strLstAdd(argList, strNewFmt("--repo1-path=%s/repo", testPath()));
strLstAdd(argList, strNewFmt("--pg1-path=%s/pg", testPath()));
strLstAddZ(argList, "restore");
harnessCfgLoad(strLstSize(argList), strLstPtr(argList));
// Create the pg path
storagePathCreateP(storagePgWrite(), NULL, .mode = 0700);
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("sparse-zero"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
true, 0x10000000000UL, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 0, true, false, NULL),
false, "zero sparse 1TB file");
TEST_RESULT_UINT(storageInfoNP(storagePg(), strNew("sparse-zero")).size, 0x10000000000UL, " check size");
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("normal-zero"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 0, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 0, false, false, NULL),
true, "zero-length file");
TEST_RESULT_UINT(storageInfoNP(storagePg(), strNew("normal-zero")).size, 0, " check size");
// -------------------------------------------------------------------------------------------------------------------------
// Create a compressed encrypted repo file
StorageWrite *ceRepoFile = storageNewWriteNP(
storageRepoWrite(), strNewFmt(STORAGE_REPO_BACKUP "/%s/%s.gz", strPtr(repoFileReferenceFull), strPtr(repoFile1)));
IoFilterGroup *filterGroup = ioFilterGroupNew();
ioFilterGroupAdd(filterGroup, gzipCompressNew(3, false));
ioFilterGroupAdd(filterGroup, cipherBlockNew(cipherModeEncrypt, cipherTypeAes256Cbc, BUFSTRDEF("badpass"), NULL));
ioWriteFilterGroupSet(storageWriteIo(ceRepoFile), filterGroup);
storagePutNP(ceRepoFile, BUFSTRDEF("acefile"));
TEST_ERROR(
restoreFile(
repoFile1, repoFileReferenceFull, true, strNew("normal"), strNew("ffffffffffffffffffffffffffffffffffffffff"),
false, 7, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 0, false, false, strNew("badpass")),
ChecksumError,
"error restoring 'normal': actual checksum 'd1cd8a7d11daa26814b93eb604e1d49ab4b43770' does not match expected checksum"
" 'ffffffffffffffffffffffffffffffffffffffff'");
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, true, strNew("normal"), strNew("d1cd8a7d11daa26814b93eb604e1d49ab4b43770"),
false, 7, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 0, false, false, strNew("badpass")),
true, "copy file");
StorageInfo info = storageInfoNP(storagePg(), strNew("normal"));
TEST_RESULT_BOOL(info.exists, true, " check exists");
TEST_RESULT_UINT(info.size, 7, " check size");
TEST_RESULT_UINT(info.mode, 0600, " check mode");
TEST_RESULT_UINT(info.timeModified, 1557432154, " check time");
TEST_RESULT_STR(strPtr(info.user), testUser(), " check user");
TEST_RESULT_STR(strPtr(info.group), testGroup(), " check group");
TEST_RESULT_STR(
strPtr(strNewBuf(storageGetNP(storageNewReadNP(storagePg(), strNew("normal"))))), "acefile", " check contents");
// -------------------------------------------------------------------------------------------------------------------------
// Create a repo file
storagePutNP(
storageNewWriteNP(
storageRepoWrite(), strNewFmt(STORAGE_REPO_BACKUP "/%s/%s", strPtr(repoFileReferenceFull), strPtr(repoFile1))),
BUFSTRDEF("atestfile"));
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("delta"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 9, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 0, true, false, NULL),
true, "sha1 delta missing");
TEST_RESULT_STR(
strPtr(strNewBuf(storageGetNP(storageNewReadNP(storagePg(), strNew("delta"))))), "atestfile", " check contents");
size_t oldBufferSize = ioBufferSize();
ioBufferSizeSet(4);
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("delta"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 9, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 0, true, false, NULL),
false, "sha1 delta existing");
ioBufferSizeSet(oldBufferSize);
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("delta"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 9, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 1557432155, true, true, NULL),
false, "sha1 delta force existing");
// Change the existing file so it no longer matches by size
storagePutNP(storageNewWriteNP(storagePgWrite(), strNew("delta")), BUFSTRDEF("atestfile2"));
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("delta"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 9, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 0, true, false, NULL),
true, "sha1 delta existing, size differs");
TEST_RESULT_STR(
strPtr(strNewBuf(storageGetNP(storageNewReadNP(storagePg(), strNew("delta"))))), "atestfile", " check contents");
storagePutNP(storageNewWriteNP(storagePgWrite(), strNew("delta")), BUFSTRDEF("atestfile2"));
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("delta"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 9, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 1557432155, true, true, NULL),
true, "delta force existing, size differs");
TEST_RESULT_STR(
strPtr(strNewBuf(storageGetNP(storageNewReadNP(storagePg(), strNew("delta"))))), "atestfile", " check contents");
// Change the existing file so it no longer matches by content
storagePutNP(storageNewWriteNP(storagePgWrite(), strNew("delta")), BUFSTRDEF("btestfile"));
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("delta"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 9, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 0, true, false, NULL),
true, "sha1 delta existing, content differs");
TEST_RESULT_STR(
strPtr(strNewBuf(storageGetNP(storageNewReadNP(storagePg(), strNew("delta"))))), "atestfile", " check contents");
storagePutNP(storageNewWriteNP(storagePgWrite(), strNew("delta")), BUFSTRDEF("btestfile"));
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("delta"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 9, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 1557432155, true, true, NULL),
true, "delta force existing, timestamp differs");
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("delta"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 9, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 1557432153, true, true, NULL),
true, "delta force existing, timestamp after copy time");
// Change the existing file to zero-length
storagePutNP(storageNewWriteNP(storagePgWrite(), strNew("delta")), BUFSTRDEF(""));
TEST_RESULT_BOOL(
restoreFile(
repoFile1, repoFileReferenceFull, false, strNew("delta"), strNew("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"),
false, 0, 1557432154, 0600, strNew(testUser()), strNew(testGroup()), 0, true, false, NULL),
false, "sha1 delta existing, content differs");
// Check protocol function directly
// -------------------------------------------------------------------------------------------------------------------------
VariantList *paramList = varLstNew();
varLstAdd(paramList, varNewStrZ("protocol"));
varLstAdd(paramList, varNewUInt64(9));
varLstAdd(paramList, varNewUInt64(1557432100));
varLstAdd(paramList, varNewStrZ("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"));
varLstAdd(paramList, varNewBool(false));
varLstAdd(paramList, varNewBool(false));
varLstAdd(paramList, varNewStr(repoFile1));
varLstAdd(paramList, NULL);
varLstAdd(paramList, varNewStrZ("0677"));
varLstAdd(paramList, varNewStrZ(testUser()));
varLstAdd(paramList, varNewStrZ(testGroup()));
varLstAdd(paramList, varNewUInt64(1557432200));
varLstAdd(paramList, varNewBool(false));
varLstAdd(paramList, varNewStr(repoFileReferenceFull));
varLstAdd(paramList, varNewBool(false));
TEST_RESULT_BOOL(restoreProtocol(PROTOCOL_COMMAND_RESTORE_FILE_STR, paramList, server), true, "protocol restore file");
TEST_RESULT_STR(strPtr(strNewBuf(serverWrite)), "{\"out\":true}\n", " check result");
bufUsedSet(serverWrite, 0);
info = storageInfoNP(storagePg(), strNew("protocol"));
TEST_RESULT_BOOL(info.exists, true, " check exists");
TEST_RESULT_UINT(info.size, 9, " check size");
TEST_RESULT_UINT(info.mode, 0677, " check mode");
TEST_RESULT_UINT(info.timeModified, 1557432100, " check time");
TEST_RESULT_STR(strPtr(info.user), testUser(), " check user");
TEST_RESULT_STR(strPtr(info.group), testGroup(), " check group");
TEST_RESULT_STR(
strPtr(strNewBuf(storageGetNP(storageNewReadNP(storagePg(), strNew("protocol"))))), "atestfile", " check contents");
paramList = varLstNew();
varLstAdd(paramList, varNewStrZ("protocol"));
varLstAdd(paramList, varNewUInt64(9));
varLstAdd(paramList, varNewUInt64(1557432100));
varLstAdd(paramList, varNewStrZ("9bc8ab2dda60ef4beed07d1e19ce0676d5edde67"));
varLstAdd(paramList, varNewBool(false));
varLstAdd(paramList, varNewBool(false));
varLstAdd(paramList, varNewStr(repoFile1));
varLstAdd(paramList, varNewStr(repoFileReferenceFull));
varLstAdd(paramList, varNewStrZ("0677"));
varLstAdd(paramList, varNewStrZ(testUser()));
varLstAdd(paramList, varNewStrZ(testGroup()));
varLstAdd(paramList, varNewUInt64(1557432200));
varLstAdd(paramList, varNewBool(true));
varLstAdd(paramList, NULL);
varLstAdd(paramList, varNewBool(false));
varLstAdd(paramList, NULL);
TEST_RESULT_BOOL(restoreProtocol(PROTOCOL_COMMAND_RESTORE_FILE_STR, paramList, server), true, "protocol restore file");
TEST_RESULT_STR(strPtr(strNewBuf(serverWrite)), "{\"out\":false}\n", " check result");
bufUsedSet(serverWrite, 0);
// Check invalid protocol function
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_BOOL(restoreProtocol(strNew(BOGUS_STR), paramList, server), false, "invalid function");
}
FUNCTION_HARNESS_RESULT_VOID();
}