/*
* 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/volumeGeometry.c#10 $
*/
#include "volumeGeometry.h"
#include "buffer.h"
#include "logger.h"
#include "memoryAlloc.h"
#include "numeric.h"
#include "permassert.h"
#include "constants.h"
#include "header.h"
#include "physicalLayer.h"
#include "releaseVersions.h"
#include "statusCodes.h"
#include "types.h"
enum {
GEOMETRY_BLOCK_LOCATION = 0,
MAGIC_NUMBER_SIZE = 8,
};
typedef struct {
char magicNumber[MAGIC_NUMBER_SIZE];
Header header;
VolumeGeometry geometry;
CRC32Checksum checksum;
} __attribute__((packed)) GeometryBlock;
static const Header GEOMETRY_BLOCK_HEADER_4_0 = {
.id = GEOMETRY_BLOCK,
.version = {
.majorVersion = 4,
.minorVersion = 0,
},
// Note: this size isn't just the payload size following the header, like it
// is everywhere else in VDO.
.size = sizeof(GeometryBlock),
};
static const byte MAGIC_NUMBER[MAGIC_NUMBER_SIZE + 1] = "dmvdo001";
static const ReleaseVersionNumber COMPATIBLE_RELEASE_VERSIONS[] = {
MAGNESIUM_RELEASE_VERSION_NUMBER,
};
/**
* Determine whether the supplied release version can be understood by
* the VDO code.
*
* @param version The release version number to check
*
* @return <code>True</code> if the given version can be loaded.
**/
static inline bool isLoadableReleaseVersion(ReleaseVersionNumber version)
{
if (version == CURRENT_RELEASE_VERSION_NUMBER) {
return true;
}
for (unsigned int i = 0; i < COUNT_OF(COMPATIBLE_RELEASE_VERSIONS); i++) {
if (version == COMPATIBLE_RELEASE_VERSIONS[i]) {
return true;
}
}
return false;
}
/**
* Decode the on-disk representation of an index configuration from a buffer.
*
* @param buffer A buffer positioned at the start of the encoding
* @param config The structure to receive the decoded fields
*
* @return UDS_SUCCESS or an error
**/
static int decodeIndexConfig(Buffer *buffer, IndexConfig *config)
{
uint32_t mem;
int result = getUInt32LEFromBuffer(buffer, &mem);
if (result != VDO_SUCCESS) {
return result;
}
uint32_t checkpointFrequency;
result = getUInt32LEFromBuffer(buffer, &checkpointFrequency);
if (result != VDO_SUCCESS) {
return result;
}
bool sparse;
result = getBoolean(buffer, &sparse);
if (result != VDO_SUCCESS) {
return result;
}
*config = (IndexConfig) {
.mem = mem,
.checkpointFrequency = checkpointFrequency,
.sparse = sparse,
};
return VDO_SUCCESS;
}
/**
* Encode the on-disk representation of an index configuration into a buffer.
*
* @param config The index configuration to encode
* @param buffer A buffer positioned at the start of the encoding
*
* @return UDS_SUCCESS or an error
**/
static int encodeIndexConfig(const IndexConfig *config, Buffer *buffer)
{
int result = putUInt32LEIntoBuffer(buffer, config->mem);
if (result != VDO_SUCCESS) {
return result;
}
result = putUInt32LEIntoBuffer(buffer, config->checkpointFrequency);
if (result != VDO_SUCCESS) {
return result;
}
return putBoolean(buffer, config->sparse);
}
/**
* Decode the on-disk representation of a volume region from a buffer.
*
* @param buffer A buffer positioned at the start of the encoding
* @param region The structure to receive the decoded fields
*
* @return UDS_SUCCESS or an error
**/
static int decodeVolumeRegion(Buffer *buffer, VolumeRegion *region)
{
VolumeRegionID id;
int result = getUInt32LEFromBuffer(buffer, &id);
if (result != VDO_SUCCESS) {
return result;
}
PhysicalBlockNumber startBlock;
result = getUInt64LEFromBuffer(buffer, &startBlock);
if (result != VDO_SUCCESS) {
return result;
}
*region = (VolumeRegion) {
.id = id,
.startBlock = startBlock,
};
return VDO_SUCCESS;
}
/**
* Encode the on-disk representation of a volume region into a buffer.
*
* @param region The region to encode
* @param buffer A buffer positioned at the start of the encoding
*
* @return UDS_SUCCESS or an error
**/
static int encodeVolumeRegion(const VolumeRegion *region, Buffer *buffer)
{
int result = putUInt32LEIntoBuffer(buffer, region->id);
if (result != VDO_SUCCESS) {
return result;
}
return putUInt64LEIntoBuffer(buffer, region->startBlock);
}
/**
* Decode the on-disk representation of a volume geometry from a buffer.
*
* @param buffer A buffer positioned at the start of the encoding
* @param geometry The structure to receive the decoded fields
*
* @return UDS_SUCCESS or an error
**/
static int decodeVolumeGeometry(Buffer *buffer, VolumeGeometry *geometry)
{
ReleaseVersionNumber releaseVersion;
int result = getUInt32LEFromBuffer(buffer, &releaseVersion);
if (result != VDO_SUCCESS) {
return result;
}
Nonce nonce;
result = getUInt64LEFromBuffer(buffer, &nonce);
if (result != VDO_SUCCESS) {
return result;
}
geometry->releaseVersion = releaseVersion;
geometry->nonce = nonce;
result = getBytesFromBuffer(buffer, sizeof(UUID), geometry->uuid);
if (result != VDO_SUCCESS) {
return result;
}
for (VolumeRegionID id = 0; id < VOLUME_REGION_COUNT; id++) {
result = decodeVolumeRegion(buffer, &geometry->regions[id]);
if (result != VDO_SUCCESS) {
return result;
}
}
return decodeIndexConfig(buffer, &geometry->indexConfig);
}
/**
* Encode the on-disk representation of a volume geometry into a buffer.
*
* @param geometry The geometry to encode
* @param buffer A buffer positioned at the start of the encoding
*
* @return UDS_SUCCESS or an error
**/
static int encodeVolumeGeometry(const VolumeGeometry *geometry, Buffer *buffer)
{
int result = putUInt32LEIntoBuffer(buffer, geometry->releaseVersion);
if (result != VDO_SUCCESS) {
return result;
}
result = putUInt64LEIntoBuffer(buffer, geometry->nonce);
if (result != VDO_SUCCESS) {
return result;
}
result = putBytes(buffer, sizeof(UUID), geometry->uuid);
if (result != VDO_SUCCESS) {
return result;
}
for (VolumeRegionID id = 0; id < VOLUME_REGION_COUNT; id++) {
result = encodeVolumeRegion(&geometry->regions[id], buffer);
if (result != VDO_SUCCESS) {
return result;
}
}
return encodeIndexConfig(&geometry->indexConfig, buffer);
}
/**
* Decode the on-disk representation of a geometry block, up to but not
* including the checksum, from a buffer.
*
* @param buffer A buffer positioned at the start of the block
* @param geometry The structure to receive the decoded volume geometry fields
*
* @return UDS_SUCCESS or an error
**/
static int decodeGeometryBlock(Buffer *buffer, VolumeGeometry *geometry)
{
if (!hasSameBytes(buffer, MAGIC_NUMBER, MAGIC_NUMBER_SIZE)) {
return VDO_BAD_MAGIC;
}
int result = skipForward(buffer, MAGIC_NUMBER_SIZE);
if (result != VDO_SUCCESS) {
return result;
}
Header header;
result = decodeHeader(buffer, &header);
if (result != VDO_SUCCESS) {
return result;
}
result = validateHeader(&GEOMETRY_BLOCK_HEADER_4_0, &header, true, __func__);
if (result != VDO_SUCCESS) {
return result;
}
result = decodeVolumeGeometry(buffer, geometry);
if (result != VDO_SUCCESS) {
return result;
}
// Leave the CRC for the caller to decode and verify.
return ASSERT(header.size
== (uncompactedAmount(buffer) + sizeof(CRC32Checksum)),
"should have decoded up to the geometry checksum");
}
/**
* Encode the on-disk representation of a geometry block, up to but not
* including the checksum, into a buffer.
*
* @param geometry The volume geometry to encode into the block
* @param buffer A buffer positioned at the start of the block
*
* @return UDS_SUCCESS or an error
**/
static int encodeGeometryBlock(const VolumeGeometry *geometry, Buffer *buffer)
{
int result = putBytes(buffer, MAGIC_NUMBER_SIZE, MAGIC_NUMBER);
if (result != VDO_SUCCESS) {
return result;
}
result = encodeHeader(&GEOMETRY_BLOCK_HEADER_4_0, buffer);
if (result != VDO_SUCCESS) {
return result;
}
result = encodeVolumeGeometry(geometry, buffer);
if (result != VDO_SUCCESS) {
return result;
}
// Leave the CRC for the caller to compute and encode.
return ASSERT(GEOMETRY_BLOCK_HEADER_4_0.size
== (contentLength(buffer) + sizeof(CRC32Checksum)),
"should have decoded up to the geometry checksum");
}
/**
* Allocate a block-size buffer to read the geometry from the physical layer,
* read the block, and return the buffer.
*
* @param [in] layer The physical layer containing the block to read
* @param [out] blockPtr A pointer to receive the allocated buffer
*
* @return VDO_SUCCESS or an error code
**/
static int readGeometryBlock(PhysicalLayer *layer, byte **blockPtr)
{
int result = ASSERT(layer->reader != NULL, "Layer must have a sync reader");
if (result != VDO_SUCCESS) {
return result;
}
char *block;
result = layer->allocateIOBuffer(layer, VDO_BLOCK_SIZE, "geometry block",
&block);
if (result != VDO_SUCCESS) {
return result;
}
result = layer->reader(layer, GEOMETRY_BLOCK_LOCATION, 1, block, NULL);
if (result != VDO_SUCCESS) {
FREE(block);
return result;
}
*blockPtr = (byte *) block;
return VDO_SUCCESS;
}
/**********************************************************************/
int loadVolumeGeometry(PhysicalLayer *layer, VolumeGeometry *geometry)
{
byte *block;
int result = readGeometryBlock(layer, &block);
if (result != VDO_SUCCESS) {
return result;
}
Buffer *buffer;
result = wrapBuffer(block, VDO_BLOCK_SIZE, VDO_BLOCK_SIZE, &buffer);
if (result != VDO_SUCCESS) {
FREE(block);
return result;
}
result = decodeGeometryBlock(buffer, geometry);
if (result != VDO_SUCCESS) {
freeBuffer(&buffer);
FREE(block);
return result;
}
// Checksum everything decoded so far.
CRC32Checksum checksum = layer->updateCRC32(INITIAL_CHECKSUM, block,
uncompactedAmount(buffer));
CRC32Checksum savedChecksum;
result = getUInt32LEFromBuffer(buffer, &savedChecksum);
if (result != VDO_SUCCESS) {
freeBuffer(&buffer);
FREE(block);
return result;
}
// Finished all decoding. Everything that follows is validation code.
freeBuffer(&buffer);
FREE(block);
if (!isLoadableReleaseVersion(geometry->releaseVersion)) {
return logErrorWithStringError(VDO_UNSUPPORTED_VERSION,
"release version %d cannot be loaded",
geometry->releaseVersion);
}
return ((checksum == savedChecksum) ? VDO_SUCCESS : VDO_CHECKSUM_MISMATCH);
}
/************************************************************************/
int computeIndexBlocks(IndexConfig *indexConfig, BlockCount *indexBlocksPtr)
{
UdsConfiguration udsConfiguration = NULL;
int result = indexConfigToUdsConfiguration(indexConfig, &udsConfiguration);
if (result != UDS_SUCCESS) {
return logErrorWithStringError(result, "error creating index config");
}
uint64_t indexBytes;
result = udsComputeIndexSize(udsConfiguration, 0, &indexBytes);
udsFreeConfiguration(udsConfiguration);
if (result != UDS_SUCCESS) {
return logErrorWithStringError(result, "error computing index size");
}
BlockCount indexBlocks = indexBytes / VDO_BLOCK_SIZE;
if ((((uint64_t) indexBlocks) * VDO_BLOCK_SIZE) != indexBytes) {
return logErrorWithStringError(VDO_PARAMETER_MISMATCH, "index size must be"
" a multiple of block size %d",
VDO_BLOCK_SIZE);
}
*indexBlocksPtr = indexBlocks;
return VDO_SUCCESS;
}
/**********************************************************************/
int initializeVolumeGeometry(Nonce nonce,
UUID uuid,
IndexConfig *indexConfig,
VolumeGeometry *geometry)
{
BlockCount indexSize = 0;
if (indexConfig != NULL) {
int result = computeIndexBlocks(indexConfig, &indexSize);
if (result != VDO_SUCCESS) {
return result;
}
}
*geometry = (VolumeGeometry) {
.releaseVersion = CURRENT_RELEASE_VERSION_NUMBER,
.nonce = nonce,
.regions = {
[INDEX_REGION] = {
.id = INDEX_REGION,
.startBlock = 1,
},
[DATA_REGION] = {
.id = DATA_REGION,
.startBlock = 1 + indexSize,
}
}
};
memcpy(geometry->uuid, uuid, sizeof(UUID));
if (indexSize > 0) {
memcpy(&geometry->indexConfig, indexConfig, sizeof(IndexConfig));
}
return VDO_SUCCESS;
}
/**********************************************************************/
int clearVolumeGeometry(PhysicalLayer *layer)
{
char *block;
int result = layer->allocateIOBuffer(layer, VDO_BLOCK_SIZE, "geometry block",
&block);
if (result != VDO_SUCCESS) {
return result;
}
result = layer->writer(layer, GEOMETRY_BLOCK_LOCATION, 1, block, NULL);
FREE(block);
return result;
}
/**********************************************************************/
int writeVolumeGeometry(PhysicalLayer *layer, VolumeGeometry *geometry)
{
char *block;
int result = layer->allocateIOBuffer(layer, VDO_BLOCK_SIZE, "geometry block",
&block);
if (result != VDO_SUCCESS) {
return result;
}
Buffer *buffer;
result = wrapBuffer((byte *) block, VDO_BLOCK_SIZE, 0, &buffer);
if (result != VDO_SUCCESS) {
FREE(block);
return result;
}
result = encodeGeometryBlock(geometry, buffer);
if (result != VDO_SUCCESS) {
freeBuffer(&buffer);
FREE(block);
return result;
}
// Checksum everything encoded so far and then encode the checksum.
CRC32Checksum checksum = layer->updateCRC32(INITIAL_CHECKSUM, (byte *) block,
contentLength(buffer));
result = putUInt32LEIntoBuffer(buffer, checksum);
if (result != VDO_SUCCESS) {
freeBuffer(&buffer);
FREE(block);
return result;
}
// Write it.
result = layer->writer(layer, GEOMETRY_BLOCK_LOCATION, 1, block, NULL);
freeBuffer(&buffer);
FREE(block);
return result;
}
/************************************************************************/
int indexConfigToUdsConfiguration(IndexConfig *indexConfig,
UdsConfiguration *udsConfigPtr)
{
UdsConfiguration udsConfiguration;
int result = udsInitializeConfiguration(&udsConfiguration, indexConfig->mem);
if (result != UDS_SUCCESS) {
return logErrorWithStringError(result, "error initializing configuration");
}
udsConfigurationSetSparse(udsConfiguration, indexConfig->sparse);
*udsConfigPtr = udsConfiguration;
return VDO_SUCCESS;
}
/************************************************************************/
void indexConfigToUdsParameters(IndexConfig *indexConfig,
struct uds_parameters *userParams)
{
userParams->checkpoint_frequency = indexConfig->checkpointFrequency;
}