/*
* Copyright (c) 2020 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* $Id: //eng/vdo-releases/aluminum/src/c++/vdo/base/superBlock.c#5 $
*/
#include "superBlock.h"
#include "buffer.h"
#include "logger.h"
#include "memoryAlloc.h"
#include "permassert.h"
#include "completion.h"
#include "constants.h"
#include "header.h"
#include "releaseVersions.h"
#include "statusCodes.h"
#include "types.h"
#include "vio.h"
struct superBlock {
/** The parent for asynchronous load and save operations */
VDOCompletion *parent;
/** The VIO for reading and writing the super block to disk */
VIO *vio;
/** The buffer for encoding and decoding component data */
Buffer *componentBuffer;
/**
* A sector-sized buffer wrapping the first sector of encodedSuperBlock, for
* encoding and decoding the entire super block.
**/
Buffer *blockBuffer;
/** A 1-block buffer holding the encoded on-disk super block */
byte *encodedSuperBlock;
/** The release version number loaded from the volume */
ReleaseVersionNumber loadedReleaseVersion;
/** Whether this super block may not be written */
bool unwriteable;
};
enum {
SUPER_BLOCK_FIXED_SIZE
= ENCODED_HEADER_SIZE + sizeof(ReleaseVersionNumber) + CHECKSUM_SIZE,
MAX_COMPONENT_DATA_SIZE = VDO_SECTOR_SIZE - SUPER_BLOCK_FIXED_SIZE,
};
static const Header SUPER_BLOCK_HEADER_12_0 = {
.id = SUPER_BLOCK,
.version = {
.majorVersion = 12,
.minorVersion = 0,
},
// This is the minimum size, if the super block contains no components.
.size = SUPER_BLOCK_FIXED_SIZE - ENCODED_HEADER_SIZE,
};
/**
* Allocate a super block. Callers must free the allocated super block even
* on error.
*
* @param layer The physical layer which holds the super block on disk
* @param superBlockPtr A pointer to hold the new super block
*
* @return VDO_SUCCESS or an error
**/
__attribute__((warn_unused_result))
static int allocateSuperBlock(PhysicalLayer *layer, SuperBlock **superBlockPtr)
{
int result = ALLOCATE(1, SuperBlock, __func__, superBlockPtr);
if (result != UDS_SUCCESS) {
return result;
}
SuperBlock *superBlock = *superBlockPtr;
result = makeBuffer(MAX_COMPONENT_DATA_SIZE, &superBlock->componentBuffer);
if (result != UDS_SUCCESS) {
return result;
}
result = layer->allocateIOBuffer(layer, VDO_BLOCK_SIZE,
"encoded super block",
(char **) &superBlock->encodedSuperBlock);
if (result != UDS_SUCCESS) {
return result;
}
// Even though the buffer is a full block, to avoid the potential corruption
// from a torn write, the entire encoding must fit in the first sector.
result = wrapBuffer(superBlock->encodedSuperBlock, VDO_SECTOR_SIZE, 0,
&superBlock->blockBuffer);
if (result != UDS_SUCCESS) {
return result;
}
if (layer->createMetadataVIO == NULL) {
return VDO_SUCCESS;
}
return createVIO(layer, VIO_TYPE_SUPER_BLOCK, VIO_PRIORITY_METADATA,
superBlock, (char *) superBlock->encodedSuperBlock,
&superBlock->vio);
}
/**********************************************************************/
int makeSuperBlock(PhysicalLayer *layer, SuperBlock **superBlockPtr)
{
SuperBlock *superBlock;
int result = allocateSuperBlock(layer, &superBlock);
if (result != VDO_SUCCESS) {
freeSuperBlock(&superBlock);
return result;
}
// For a new super block, use the current release.
superBlock->loadedReleaseVersion = CURRENT_RELEASE_VERSION_NUMBER;
*superBlockPtr = superBlock;
return VDO_SUCCESS;
}
/**********************************************************************/
void freeSuperBlock(SuperBlock **superBlockPtr)
{
if (*superBlockPtr == NULL) {
return;
}
SuperBlock *superBlock = *superBlockPtr;
freeBuffer(&superBlock->blockBuffer);
freeBuffer(&superBlock->componentBuffer);
freeVIO(&superBlock->vio);
FREE(superBlock->encodedSuperBlock);
FREE(superBlock);
*superBlockPtr = NULL;
}
/**
* Encode a super block into its on-disk representation.
*
* @param layer The physical layer which implements the checksum
* @param superBlock The super block to encode
*
* @return VDO_SUCCESS or an error
**/
__attribute__((warn_unused_result))
static int encodeSuperBlock(PhysicalLayer *layer, SuperBlock *superBlock)
{
Buffer *buffer = superBlock->blockBuffer;
int result = resetBufferEnd(buffer, 0);
if (result != VDO_SUCCESS) {
return result;
}
size_t componentDataSize = contentLength(superBlock->componentBuffer);
// Encode the header.
Header header = SUPER_BLOCK_HEADER_12_0;
header.size += componentDataSize;
result = encodeHeader(&header, buffer);
if (result != UDS_SUCCESS) {
return result;
}
// Encode the loaded release version.
result = putUInt32LEIntoBuffer(buffer, superBlock->loadedReleaseVersion);
if (result != UDS_SUCCESS) {
return result;
}
// Copy the already-encoded component data.
result = putBytes(buffer, componentDataSize,
getBufferContents(superBlock->componentBuffer));
if (result != UDS_SUCCESS) {
return result;
}
// Compute and encode the checksum.
CRC32Checksum checksum = layer->updateCRC32(INITIAL_CHECKSUM,
superBlock->encodedSuperBlock,
contentLength(buffer));
result = putUInt32LEIntoBuffer(buffer, checksum);
if (result != UDS_SUCCESS) {
return result;
}
return UDS_SUCCESS;
}
/**********************************************************************/
int saveSuperBlock(PhysicalLayer *layer,
SuperBlock *superBlock,
PhysicalBlockNumber superBlockOffset)
{
int result = encodeSuperBlock(layer, superBlock);
if (result != VDO_SUCCESS) {
return result;
}
return layer->writer(layer, superBlockOffset, 1,
(char *) superBlock->encodedSuperBlock, NULL);
}
/**
* Finish the parent of a super block load or save operation. This
* callback is registered in saveSuperBlockAsync() and loadSuperBlockAsync.
*
* @param completion The super block VIO
**/
static void finishSuperBlockParent(VDOCompletion *completion)
{
SuperBlock *superBlock = completion->parent;
VDOCompletion *parent = superBlock->parent;
superBlock->parent = NULL;
finishCompletion(parent, completion->result);
}
/**
* Log a super block save error. This error handler is registered in
* saveSuperBlockAsync().
*
* @param completion The super block VIO
**/
static void handleSaveError(VDOCompletion *completion)
{
logErrorWithStringError(completion->result, "super block save failed");
/*
* Mark the super block as unwritable so that we won't attempt to write it
* again. This avoids the case where a growth attempt fails writing the
* super block with the new size, but the subsequent attempt to write out
* the read-only state succeeds. In this case, writes which happened just
* before the suspend would not be visible if the VDO is restarted without
* rebuilding, but, after a read-only rebuild, the effects of those writes
* would reappear.
*/
((SuperBlock *) completion->parent)->unwriteable = true;
completion->callback(completion);
}
/**********************************************************************/
void saveSuperBlockAsync(SuperBlock *superBlock,
PhysicalBlockNumber superBlockOffset,
VDOCompletion *parent)
{
if (superBlock->unwriteable) {
finishCompletion(parent, VDO_READ_ONLY);
return;
}
if (superBlock->parent != NULL) {
finishCompletion(parent, VDO_COMPONENT_BUSY);
return;
}
PhysicalLayer *layer = parent->layer;
int result = encodeSuperBlock(layer, superBlock);
if (result != VDO_SUCCESS) {
finishCompletion(parent, result);
return;
}
superBlock->parent = parent;
superBlock->vio->completion.callbackThreadID = parent->callbackThreadID;
launchWriteMetadataVIOWithFlush(superBlock->vio, superBlockOffset,
finishSuperBlockParent, handleSaveError,
true, true);
}
/**
* Decode a super block from its on-disk representation.
*
* @param layer The physical layer which implements the checksum
* @param superBlock The super block to decode
*
* @return VDO_SUCCESS or an error
**/
__attribute__((warn_unused_result))
static int decodeSuperBlock(PhysicalLayer *layer, SuperBlock *superBlock)
{
// Reset the block buffer to start decoding the entire first sector.
Buffer *buffer = superBlock->blockBuffer;
clearBuffer(buffer);
// Decode and validate the header.
Header header;
int result = decodeHeader(buffer, &header);
if (result != VDO_SUCCESS) {
return result;
}
result = validateHeader(&SUPER_BLOCK_HEADER_12_0, &header, false, __func__);
if (result != VDO_SUCCESS) {
return result;
}
if (header.size > contentLength(buffer)) {
// We can't check release version or checksum until we know the content
// size, so we have to assume a version mismatch on unexpected values.
return logErrorWithStringError(VDO_UNSUPPORTED_VERSION,
"super block contents too large: %zu",
header.size);
}
// Restrict the buffer to the actual payload bytes that remain.
result = resetBufferEnd(buffer, uncompactedAmount(buffer) + header.size);
if (result != VDO_SUCCESS) {
return result;
}
// Decode and store the release version number. It will be checked when the
// VDO master version is decoded and validated.
result = getUInt32LEFromBuffer(buffer, &superBlock->loadedReleaseVersion);
if (result != VDO_SUCCESS) {
return result;
}
// The component data is all the rest, except for the checksum.
size_t componentDataSize = contentLength(buffer) - sizeof(CRC32Checksum);
result = putBuffer(superBlock->componentBuffer, buffer, componentDataSize);
if (result != VDO_SUCCESS) {
return result;
}
// Checksum everything up to but not including the saved checksum itself.
CRC32Checksum checksum = layer->updateCRC32(INITIAL_CHECKSUM,
superBlock->encodedSuperBlock,
uncompactedAmount(buffer));
// Decode and verify the saved checksum.
CRC32Checksum savedChecksum;
result = getUInt32LEFromBuffer(buffer, &savedChecksum);
if (result != VDO_SUCCESS) {
return result;
}
result = ASSERT(contentLength(buffer) == 0,
"must have decoded entire superblock payload");
if (result != VDO_SUCCESS) {
return result;
}
return ((checksum != savedChecksum) ? VDO_CHECKSUM_MISMATCH : VDO_SUCCESS);
}
/**********************************************************************/
int loadSuperBlock(PhysicalLayer *layer,
PhysicalBlockNumber superBlockOffset,
SuperBlock **superBlockPtr)
{
SuperBlock *superBlock = NULL;
int result = allocateSuperBlock(layer, &superBlock);
if (result != VDO_SUCCESS) {
freeSuperBlock(&superBlock);
return result;
}
result = layer->reader(layer, superBlockOffset, 1,
(char *) superBlock->encodedSuperBlock, NULL);
if (result != VDO_SUCCESS) {
freeSuperBlock(&superBlock);
return result;
}
result = decodeSuperBlock(layer, superBlock);
if (result != VDO_SUCCESS) {
freeSuperBlock(&superBlock);
return result;
}
*superBlockPtr = superBlock;
return result;
}
/**
* Continue after loading the super block. This callback is registered
* in loadSuperBlockAsync().
*
* @param completion The super block VIO
**/
static void finishReadingSuperBlock(VDOCompletion *completion)
{
SuperBlock *superBlock = completion->parent;
VDOCompletion *parent = superBlock->parent;
superBlock->parent = NULL;
finishCompletion(parent, decodeSuperBlock(completion->layer, superBlock));
}
/**********************************************************************/
void loadSuperBlockAsync(VDOCompletion *parent,
PhysicalBlockNumber superBlockOffset,
SuperBlock **superBlockPtr)
{
PhysicalLayer *layer = parent->layer;
SuperBlock *superBlock = NULL;
int result = allocateSuperBlock(layer, &superBlock);
if (result != VDO_SUCCESS) {
freeSuperBlock(&superBlock);
finishCompletion(parent, result);
return;
}
*superBlockPtr = superBlock;
superBlock->parent = parent;
superBlock->vio->completion.callbackThreadID = parent->callbackThreadID;
launchReadMetadataVIO(superBlock->vio, superBlockOffset,
finishReadingSuperBlock, finishSuperBlockParent);
}
/**********************************************************************/
Buffer *getComponentBuffer(SuperBlock *superBlock)
{
return superBlock->componentBuffer;
}
/**********************************************************************/
ReleaseVersionNumber getLoadedReleaseVersion(const SuperBlock *superBlock)
{
return superBlock->loadedReleaseVersion;
}
/**********************************************************************/
size_t getFixedSuperBlockSize(void)
{
return SUPER_BLOCK_FIXED_SIZE;
}