1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-11-06 08:49:29 +02:00
Files
pgbackrest/src/command/restore/blockDelta.c
David Steele f4a4af299b Fix short read in block incremental restore.
During restore it is possible to read all the blocks out of a compressed super block without reading all the input. This is because the compression format may have some trailing bytes that are not required for decompression but are required to indicate that data has ended. If a buffer aligned with the compressed data in a certain way, these last bytes might not be read.

Explicitly read out any final bytes at the end of each super block to handle this case. This should always result in no additional data out and we check for that, but it does move the read position to the beginning of the next compressed super block so decompression can begin without error.
2023-12-27 12:49:47 -03:00

317 lines
14 KiB
C

/***********************************************************************************************************************************
Block Restore
***********************************************************************************************************************************/
#include "build.auto.h"
#include "command/backup/blockIncr.h"
#include "command/restore/blockDelta.h"
#include "common/crypto/cipherBlock.h"
#include "common/debug.h"
#include "common/io/limitRead.h"
#include "common/log.h"
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
typedef struct BlockDeltaSuperBlock
{
uint64_t superBlockSize; // Super block size
uint64_t size; // Stored size of superblock (with compression, etc.)
List *blockList; // Block list
} BlockDeltaSuperBlock;
typedef struct BlockDeltaBlock
{
uint64_t no; // Block number in the super block
uint64_t offset; // Offset into original file
unsigned char checksum[XX_HASH_SIZE_MAX]; // Checksum of the block
} BlockDeltaBlock;
struct BlockDelta
{
BlockDeltaPub pub; // Publicly accessible variables
size_t blockSize; // Block size
size_t checksumSize; // Checksum size
CipherType cipherType; // Cipher type
String *cipherPass; // Cipher passphrase
CompressType compressType; // Compress type
const BlockDeltaSuperBlock *superBlockData; // Current super block data
unsigned int superBlockIdx; // Current super block index
IoRead *limitRead; // Limit read for current super block
const BlockDeltaBlock *blockData; // Current block data
unsigned int blockIdx; // Current block index
unsigned int blockTotal; // Block total for super block
unsigned int blockFindIdx; // Index of the block to find in the super block
BlockDeltaWrite write; // Block/offset to be returned for write
};
/**********************************************************************************************************************************/
typedef struct BlockDeltaReference
{
unsigned int reference; // Reference
List *blockList; // List of blocks in the block map for the reference
} BlockDeltaReference;
FN_EXTERN BlockDelta *
blockDeltaNew(
const BlockMap *const blockMap, const size_t blockSize, const size_t checksumSize, const Buffer *const blockChecksum,
const CipherType cipherType, const String *const cipherPass, const CompressType compressType)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(BLOCK_MAP, blockMap);
FUNCTION_TEST_PARAM(SIZE, blockSize);
FUNCTION_TEST_PARAM(SIZE, checksumSize);
FUNCTION_TEST_PARAM(BUFFER, blockChecksum);
FUNCTION_TEST_PARAM(STRING_ID, cipherType);
FUNCTION_TEST_PARAM(STRING, cipherPass);
FUNCTION_TEST_PARAM(ENUM, compressType);
FUNCTION_TEST_END();
ASSERT(blockMap != NULL);
ASSERT(blockSize > 0);
ASSERT(cipherType == cipherTypeNone || cipherPass != NULL);
OBJ_NEW_BEGIN(BlockDelta, .childQty = MEM_CONTEXT_QTY_MAX)
{
*this = (BlockDelta)
{
.pub =
{
.readList = lstNewP(sizeof(BlockDeltaRead)),
},
.blockSize = blockSize,
.checksumSize = checksumSize,
.cipherType = cipherType,
.cipherPass = strDup(cipherPass),
.compressType = compressType,
.write =
{
.block = bufNew(blockSize),
}
};
MEM_CONTEXT_TEMP_BEGIN()
{
// Build list of references and for each reference the list of blocks for that reference
const unsigned int blockChecksumSize =
blockChecksum == NULL ? 0 : (unsigned int)(bufUsed(blockChecksum) / this->checksumSize);
List *const referenceList = lstNewP(sizeof(BlockDeltaReference), .comparator = lstComparatorUInt);
for (unsigned int blockMapIdx = 0; blockMapIdx < blockMapSize(blockMap); blockMapIdx++)
{
const BlockMapItem *const blockMapItem = blockMapGet(blockMap, blockMapIdx);
// The block must be updated if it is beyond the blocks that exist in the block checksum list or when the checksum
// stored in the repository is different from the block checksum list
if (blockMapIdx >= blockChecksumSize ||
!bufEq(
BUF(blockMapItem->checksum, this->checksumSize),
BUF(bufPtrConst(blockChecksum) + blockMapIdx * this->checksumSize, this->checksumSize)))
{
const unsigned int reference = blockMapItem->reference;
BlockDeltaReference *const referenceData = lstFind(referenceList, &reference);
// If the reference has not been added
if (referenceData == NULL)
{
BlockDeltaReference *referenceData = lstAdd(
referenceList,
&(BlockDeltaReference){.reference = reference, .blockList = lstNewP(sizeof(unsigned int))});
lstAdd(referenceData->blockList, &blockMapIdx);
}
// Else add the new block
else
lstAdd(referenceData->blockList, &blockMapIdx);
}
}
// Sort the reference list descending. This is an arbitrary choice as the order does not matter.
lstSort(referenceList, sortOrderDesc);
// Build delta
for (unsigned int referenceIdx = 0; referenceIdx < lstSize(referenceList); referenceIdx++)
{
const BlockDeltaReference *const referenceData = (const BlockDeltaReference *)lstGet(referenceList, referenceIdx);
BlockDeltaRead *blockDeltaRead = NULL;
BlockDeltaSuperBlock *blockDeltaSuperBlock = NULL;
const BlockMapItem *blockMapItemPrior = NULL;
for (unsigned int blockIdx = 0; blockIdx < lstSize(referenceData->blockList); blockIdx++)
{
const unsigned int blockMapIdx = *(unsigned int *)lstGet(referenceData->blockList, blockIdx);
const BlockMapItem *const blockMapItem = blockMapGet(blockMap, blockMapIdx);
// Add read when it has changed
if (blockMapItemPrior == NULL ||
(blockMapItemPrior->offset != blockMapItem->offset &&
blockMapItemPrior->offset + blockMapItemPrior->size != blockMapItem->offset))
{
MEM_CONTEXT_OBJ_BEGIN(this->pub.readList)
{
BlockDeltaRead blockDeltaReadNew =
{
.reference = blockMapItem->reference,
.bundleId = blockMapItem->bundleId,
.offset = blockMapItem->offset,
.superBlockList = lstNewP(sizeof(BlockDeltaSuperBlock)),
};
blockDeltaRead = lstAdd(this->pub.readList, &blockDeltaReadNew);
}
MEM_CONTEXT_OBJ_END();
}
// Add super block when it has changed
if (blockMapItemPrior == NULL || blockMapItemPrior->offset != blockMapItem->offset)
{
MEM_CONTEXT_OBJ_BEGIN(blockDeltaRead->superBlockList)
{
BlockDeltaSuperBlock blockDeltaSuperBlockNew =
{
.superBlockSize = blockMapItem->superBlockSize,
.size = blockMapItem->size,
.blockList = lstNewP(sizeof(BlockDeltaBlock)),
};
blockDeltaSuperBlock = lstAdd(blockDeltaRead->superBlockList, &blockDeltaSuperBlockNew);
blockDeltaRead->size += blockMapItem->size;
}
MEM_CONTEXT_OBJ_END();
}
// Add block
BlockDeltaBlock blockDeltaBlockNew =
{
.no = blockMapItem->block,
.offset = blockMapIdx * blockSize,
};
memcpy(blockDeltaBlockNew.checksum, blockMapItem->checksum, SIZE_OF_STRUCT_MEMBER(BlockDeltaBlock, checksum));
lstAdd(blockDeltaSuperBlock->blockList, &blockDeltaBlockNew);
// Set prior item for comparison on the next loop
blockMapItemPrior = blockMapItem;
}
}
}
MEM_CONTEXT_TEMP_END();
}
OBJ_NEW_END();
FUNCTION_TEST_RETURN(BLOCK_DELTA, this);
}
/**********************************************************************************************************************************/
FN_EXTERN const BlockDeltaWrite *
blockDeltaNext(BlockDelta *const this, const BlockDeltaRead *const readDelta, IoRead *const readIo)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(BLOCK_DELTA, this);
FUNCTION_TEST_PARAM_P(VOID, readDelta);
FUNCTION_TEST_PARAM(IO_READ, readIo);
FUNCTION_TEST_END();
FUNCTION_AUDIT_STRUCT();
ASSERT(this != NULL);
ASSERT(readDelta != NULL);
ASSERT(readIo != NULL);
BlockDeltaWrite *result = NULL;
// Iterate super blocks
while (this->superBlockIdx < lstSize(readDelta->superBlockList))
{
// If the super block read has not begun yet
if (this->superBlockData == NULL)
{
// Free prior limit read and create limit read for current super block
ioReadFree(this->limitRead);
this->superBlockData = lstGet(readDelta->superBlockList, this->superBlockIdx);
MEM_CONTEXT_OBJ_BEGIN(this)
{
this->limitRead = ioLimitReadNew(readIo, this->superBlockData->size);
}
MEM_CONTEXT_OBJ_END();
if (this->cipherType != cipherTypeNone)
{
ioFilterGroupAdd(
ioReadFilterGroup(this->limitRead),
cipherBlockNewP(cipherModeDecrypt, this->cipherType, BUFSTR(this->cipherPass), .raw = true));
}
if (this->compressType != compressTypeNone)
ioFilterGroupAdd(ioReadFilterGroup(this->limitRead), decompressFilterP(this->compressType, .raw = true));
ioReadOpen(this->limitRead);
// Set block info
this->blockIdx = 0;
this->blockFindIdx = 0;
this->blockTotal =
(unsigned int)(this->superBlockData->superBlockSize / this->blockSize) +
(this->superBlockData->superBlockSize % this->blockSize == 0 ? 0 : 1);
this->blockData = lstGet(this->superBlockData->blockList, this->blockFindIdx);
}
// Find required blocks in the super block
while (this->blockIdx < this->blockTotal)
{
// Clear buffer and read block
bufUsedZero(this->write.block);
bufLimitClear(this->write.block);
ioRead(this->limitRead, this->write.block);
// If the block matches the block we are expecting
if (this->blockIdx == this->blockData->no)
{
ASSERT(result == NULL);
this->write.offset = this->blockData->offset;
result = &this->write;
this->blockFindIdx++;
// Get the next block if there are any more to read
if (this->blockFindIdx < lstSize(this->superBlockData->blockList))
this->blockData = lstGet(this->superBlockData->blockList, this->blockFindIdx);
}
// Increment the block to read in the super block
this->blockIdx++;
// Break if there is a result
if (result != NULL)
break;
}
// Break if there is a result
if (result != NULL)
break;
// Check that no bytes remain to be written. It is possible that some bytes remain in the super block, however, since we may
// have gotten all the bytes we needed but just missed reading something important, e.g. an end of file marker. If we do not
// read the remaining bytes then the next read will start too early.
ioReadFlushP(this->limitRead, .errorOnBytes = true);
this->superBlockData = NULL;
this->superBlockIdx++;
}
// If no result then the super blocks have been read. Reset for the next read.
if (result == NULL)
{
ASSERT(this->superBlockIdx == lstSize(readDelta->superBlockList));
ASSERT(this->blockIdx == this->blockTotal);
this->superBlockData = NULL;
this->superBlockIdx = 0;
}
FUNCTION_TEST_RETURN_TYPE_P(BlockDeltaWrite, result);
}