1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2026-05-22 10:15:16 +02:00
Files
pgbackrest/src/command/restore/file.c
T
David Steele a474ba54c5 Refactoring path support in the storage module.
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.
2019-05-26 12:41:15 -04:00

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);
}