You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2026-05-22 10:15:16 +02:00
a474ba54c5
Not all storage types support paths as a physical thing that must be created/destroyed. Add a feature to determine which drivers use paths and simplify the driver API as much as possible given that knowledge and by implementing as much path logic as possible in the Storage object. Remove the ignoreMissing parameter from pathSync() since it is not used and makes little sense. Create a standard list of error messages for the drivers to use and apply them where the code was modified -- there is plenty of work still to be done here.
198 lines
8.5 KiB
C
198 lines
8.5 KiB
C
/***********************************************************************************************************************************
|
|
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/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);
|
|
}
|