/* ----> DO NOT REMOVE THE FOLLOWING NOTICE <---- | |
Copyright (c) 2014-2015 Datalight, Inc. | |
All Rights Reserved Worldwide. | |
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; use version 2 of the License. | |
This program is distributed in the hope that it will be useful, | |
but "AS-IS," 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. | |
*/ | |
/* Businesses and individuals that for commercial or other reasons cannot | |
comply with the terms of the GPLv2 license may obtain a commercial license | |
before incorporating Reliance Edge into proprietary software for | |
distribution in any form. Visit http://www.datalight.com/reliance-edge for | |
more information. | |
*/ | |
/** @file | |
@brief Implements the block device buffering system. | |
This module implements the block buffer cache. It has a number of block | |
sized buffers which are used to store data from a given block (identified | |
by both block number and volume number: this cache is shared among all | |
volumes). Block buffers may be either dirty or clean. Most I/O passes | |
through this module. When a buffer is needed for a block which is not in | |
the cache, a "victim" is selected via a simple LRU scheme. | |
*/ | |
#include <redfs.h> | |
#include <redcore.h> | |
#if DINDIR_POINTERS > 0U | |
#define INODE_META_BUFFERS 3U /* Inode, double indirect, indirect */ | |
#elif REDCONF_INDIRECT_POINTERS > 0U | |
#define INODE_META_BUFFERS 2U /* Inode, indirect */ | |
#elif REDCONF_DIRECT_POINTERS == INODE_ENTRIES | |
#define INODE_META_BUFFERS 1U /* Inode only */ | |
#endif | |
#define INODE_BUFFERS (INODE_META_BUFFERS + 1U) /* Add data buffer */ | |
#if REDCONF_IMAP_EXTERNAL == 1 | |
#define IMAP_BUFFERS 1U | |
#else | |
#define IMAP_BUFFERS 0U | |
#endif | |
#if (REDCONF_READ_ONLY == 1) || (REDCONF_API_FSE == 1) | |
/* Read, write, truncate, lookup: One inode all the way down, plus imap. | |
*/ | |
#define MINIMUM_BUFFER_COUNT (INODE_BUFFERS + IMAP_BUFFERS) | |
#elif REDCONF_API_POSIX == 1 | |
#if REDCONF_API_POSIX_RENAME == 1 | |
#if REDCONF_RENAME_ATOMIC == 1 | |
/* Two parent directories all the way down. Source and destination inode | |
buffer. One inode buffer for cyclic rename detection. Imap. The | |
parent inode buffers are released before deleting the destination | |
inode, so that does not increase the minimum. | |
*/ | |
#define MINIMUM_BUFFER_COUNT (INODE_BUFFERS + INODE_BUFFERS + 3U + IMAP_BUFFERS) | |
#else | |
/* Two parent directories all the way down. Source inode buffer. One | |
inode buffer for cyclic rename detection. Imap. | |
*/ | |
#define MINIMUM_BUFFER_COUNT (INODE_BUFFERS + INODE_BUFFERS + 2U + IMAP_BUFFERS) | |
#endif | |
#else | |
/* Link/create: Needs a parent inode all the way down, an extra inode | |
buffer, and an imap buffer. | |
Unlink is the same, since the parent inode buffers are released before | |
the inode is deleted. | |
*/ | |
#define MINIMUM_BUFFER_COUNT (INODE_BUFFERS + 1U + IMAP_BUFFERS) | |
#endif | |
#endif | |
#if REDCONF_BUFFER_COUNT < MINIMUM_BUFFER_COUNT | |
#error "REDCONF_BUFFER_COUNT is too low for the configuration" | |
#endif | |
/* A note on the typecasts in the below macros: Operands to bitwise operators | |
are subject to the "usual arithmetic conversions". This means that the | |
flags, which have uint16_t values, are promoted to int. MISRA-C:2012 R10.1 | |
forbids using signed integers in bitwise operations, so we cast to uint32_t | |
to avoid the integer promotion, then back to uint16_t to reflect the actual | |
type. | |
*/ | |
#define BFLAG_META_MASK (uint16_t)((uint32_t)BFLAG_META_MASTER | BFLAG_META_IMAP | BFLAG_META_INODE | BFLAG_META_INDIR | BFLAG_META_DINDIR) | |
#define BFLAG_MASK (uint16_t)((uint32_t)BFLAG_DIRTY | BFLAG_NEW | BFLAG_META_MASK) | |
/* An invalid block number. Used to indicate buffers which are not currently | |
in use. | |
*/ | |
#define BBLK_INVALID UINT32_MAX | |
/** @brief Metadata stored for each block buffer. | |
To make better use of CPU caching when searching the BUFFERHEAD array, this | |
structure should be kept small. | |
*/ | |
typedef struct | |
{ | |
uint32_t ulBlock; /**< Block number the buffer is associated with; BBLK_INVALID if unused. */ | |
uint8_t bVolNum; /**< Volume the block resides on. */ | |
uint8_t bRefCount; /**< Number of references. */ | |
uint16_t uFlags; /**< Buffer flags: mask of BFLAG_* values. */ | |
} BUFFERHEAD; | |
/** @brief State information for the block buffer module. | |
*/ | |
typedef struct | |
{ | |
/** Number of buffers which are referenced (have a bRefCount > 0). | |
*/ | |
uint16_t uNumUsed; | |
/** MRU array. Each element of the array stores a buffer index; each buffer | |
index appears in the array once and only once. The first element of the | |
array is the most-recently-used (MRU) buffer, followed by the next most | |
recently used, and so on, till the last element, which is the least- | |
recently-used (LRU) buffer. | |
*/ | |
uint8_t abMRU[REDCONF_BUFFER_COUNT]; | |
/** Buffer heads, storing metadata for each buffer. | |
*/ | |
BUFFERHEAD aHead[REDCONF_BUFFER_COUNT]; | |
/** Array of memory for the block buffers themselves. | |
Force 64-bit alignment of the aabBuffer array to ensure that it is safe | |
to cast buffer pointers to node structure pointers. | |
*/ | |
ALIGNED_2D_BYTE_ARRAY(b, aabBuffer, REDCONF_BUFFER_COUNT, REDCONF_BLOCK_SIZE); | |
} BUFFERCTX; | |
static bool BufferIsValid(const uint8_t *pbBuffer, uint16_t uFlags); | |
static bool BufferToIdx(const void *pBuffer, uint8_t *pbIdx); | |
#if REDCONF_READ_ONLY == 0 | |
static REDSTATUS BufferWrite(uint8_t bIdx); | |
static REDSTATUS BufferFinalize(uint8_t *pbBuffer, uint16_t uFlags); | |
#endif | |
static void BufferMakeLRU(uint8_t bIdx); | |
static void BufferMakeMRU(uint8_t bIdx); | |
static bool BufferFind(uint32_t ulBlock, uint8_t *pbIdx); | |
#ifdef REDCONF_ENDIAN_SWAP | |
static void BufferEndianSwap(const void *pBuffer, uint16_t uFlags); | |
static void BufferEndianSwapHeader(NODEHEADER *pHeader); | |
static void BufferEndianSwapMaster(MASTERBLOCK *pMaster); | |
static void BufferEndianSwapMetaRoot(METAROOT *pMetaRoot); | |
static void BufferEndianSwapInode(INODE *pInode); | |
static void BufferEndianSwapIndir(INDIR *pIndir); | |
#endif | |
static BUFFERCTX gBufCtx; | |
/** @brief Initialize the buffers. | |
*/ | |
void RedBufferInit(void) | |
{ | |
uint8_t bIdx; | |
RedMemSet(&gBufCtx, 0U, sizeof(gBufCtx)); | |
for(bIdx = 0U; bIdx < REDCONF_BUFFER_COUNT; bIdx++) | |
{ | |
/* When the buffers have been freshly initialized, acquire the buffers | |
in the order in which they appear in the array. | |
*/ | |
gBufCtx.abMRU[bIdx] = (uint8_t)((REDCONF_BUFFER_COUNT - bIdx) - 1U); | |
gBufCtx.aHead[bIdx].ulBlock = BBLK_INVALID; | |
} | |
} | |
/** @brief Acquire a buffer. | |
@param ulBlock Block number to acquire. | |
@param uFlags BFLAG_ values for the operation. | |
@param ppBuffer On success, populated with the acquired buffer. | |
@return A negated ::REDSTATUS code indicating the operation result. | |
@retval 0 Operation was successful. | |
@retval -RED_EIO A disk I/O error occurred. | |
@retval -RED_EINVAL Invalid parameters. | |
@retval -RED_EBUSY All buffers are referenced. | |
*/ | |
REDSTATUS RedBufferGet( | |
uint32_t ulBlock, | |
uint16_t uFlags, | |
void **ppBuffer) | |
{ | |
REDSTATUS ret = 0; | |
uint8_t bIdx; | |
if((ulBlock >= gpRedVolume->ulBlockCount) || ((uFlags & BFLAG_MASK) != uFlags) || (ppBuffer == NULL)) | |
{ | |
REDERROR(); | |
ret = -RED_EINVAL; | |
} | |
else | |
{ | |
if(BufferFind(ulBlock, &bIdx)) | |
{ | |
/* Error if the buffer exists and BFLAG_NEW was specified, since | |
the new flag is used when a block is newly allocated/created, so | |
the block was previously free and and there should never be an | |
existing buffer for a free block. | |
Error if the buffer exists but does not have the same type as | |
was requested. | |
*/ | |
if( ((uFlags & BFLAG_NEW) != 0U) | |
|| ((uFlags & BFLAG_META_MASK) != (gBufCtx.aHead[bIdx].uFlags & BFLAG_META_MASK))) | |
{ | |
CRITICAL_ERROR(); | |
ret = -RED_EFUBAR; | |
} | |
} | |
else if(gBufCtx.uNumUsed == REDCONF_BUFFER_COUNT) | |
{ | |
/* The MINIMUM_BUFFER_COUNT is supposed to ensure that no operation | |
ever runs out of buffers, so this should never happen. | |
*/ | |
CRITICAL_ERROR(); | |
ret = -RED_EBUSY; | |
} | |
else | |
{ | |
BUFFERHEAD *pHead; | |
/* Search for the least recently used buffer which is not | |
referenced. | |
*/ | |
for(bIdx = (uint8_t)(REDCONF_BUFFER_COUNT - 1U); bIdx > 0U; bIdx--) | |
{ | |
if(gBufCtx.aHead[gBufCtx.abMRU[bIdx]].bRefCount == 0U) | |
{ | |
break; | |
} | |
} | |
bIdx = gBufCtx.abMRU[bIdx]; | |
pHead = &gBufCtx.aHead[bIdx]; | |
if(pHead->bRefCount == 0U) | |
{ | |
/* If the LRU buffer is valid and dirty, write it out before | |
repurposing it. | |
*/ | |
if(((pHead->uFlags & BFLAG_DIRTY) != 0U) && (pHead->ulBlock != BBLK_INVALID)) | |
{ | |
#if REDCONF_READ_ONLY == 1 | |
CRITICAL_ERROR(); | |
ret = -RED_EFUBAR; | |
#else | |
ret = BufferWrite(bIdx); | |
#endif | |
} | |
} | |
else | |
{ | |
/* All the buffers are used, which should have been caught by | |
checking gBufCtx.uNumUsed. | |
*/ | |
CRITICAL_ERROR(); | |
ret = -RED_EBUSY; | |
} | |
if(ret == 0) | |
{ | |
if((uFlags & BFLAG_NEW) == 0U) | |
{ | |
/* Invalidate the LRU buffer. If the read fails, we do not | |
want the buffer head to continue to refer to the old | |
block number, since the read, even if it fails, may have | |
partially overwritten the buffer data (consider the case | |
where block size exceeds sector size, and some but not | |
all of the sectors are read successfully), and if the | |
buffer were to be used subsequently with its partially | |
erroneous contents, bad things could happen. | |
*/ | |
pHead->ulBlock = BBLK_INVALID; | |
ret = RedIoRead(gbRedVolNum, ulBlock, 1U, gBufCtx.b.aabBuffer[bIdx]); | |
if((ret == 0) && ((uFlags & BFLAG_META) != 0U)) | |
{ | |
if(!BufferIsValid(gBufCtx.b.aabBuffer[bIdx], uFlags)) | |
{ | |
/* A corrupt metadata node is usually a critical | |
error. The master block is an exception since | |
it might be invalid because the volume is not | |
mounted; that condition is expected and should | |
not result in an assertion. | |
*/ | |
CRITICAL_ASSERT((uFlags & BFLAG_META_MASTER) != 0U); | |
ret = -RED_EIO; | |
} | |
} | |
#ifdef REDCONF_ENDIAN_SWAP | |
if(ret == 0) | |
{ | |
BufferEndianSwap(gBufCtx.b.aabBuffer[bIdx], uFlags); | |
} | |
#endif | |
} | |
else | |
{ | |
RedMemSet(gBufCtx.b.aabBuffer[bIdx], 0U, REDCONF_BLOCK_SIZE); | |
} | |
} | |
if(ret == 0) | |
{ | |
pHead->bVolNum = gbRedVolNum; | |
pHead->ulBlock = ulBlock; | |
pHead->uFlags = 0U; | |
} | |
} | |
/* Reference the buffer, update its flags, and promote it to MRU. This | |
happens both when BufferFind() found an existing buffer for the | |
block and when the LRU buffer was repurposed to create a buffer for | |
the block. | |
*/ | |
if(ret == 0) | |
{ | |
BUFFERHEAD *pHead = &gBufCtx.aHead[bIdx]; | |
pHead->bRefCount++; | |
if(pHead->bRefCount == 1U) | |
{ | |
gBufCtx.uNumUsed++; | |
} | |
/* BFLAG_NEW tells this function to zero the buffer instead of | |
reading it from disk; it has no meaning later on, and thus is | |
not saved. | |
*/ | |
pHead->uFlags |= (uFlags & (~BFLAG_NEW)); | |
BufferMakeMRU(bIdx); | |
*ppBuffer = gBufCtx.b.aabBuffer[bIdx]; | |
} | |
} | |
return ret; | |
} | |
/** @brief Release a buffer. | |
@param pBuffer The buffer to release. | |
*/ | |
void RedBufferPut( | |
const void *pBuffer) | |
{ | |
uint8_t bIdx; | |
if(!BufferToIdx(pBuffer, &bIdx)) | |
{ | |
REDERROR(); | |
} | |
else | |
{ | |
REDASSERT(gBufCtx.aHead[bIdx].bRefCount > 0U); | |
gBufCtx.aHead[bIdx].bRefCount--; | |
if(gBufCtx.aHead[bIdx].bRefCount == 0U) | |
{ | |
REDASSERT(gBufCtx.uNumUsed > 0U); | |
gBufCtx.uNumUsed--; | |
} | |
} | |
} | |
#if REDCONF_READ_ONLY == 0 | |
/** @brief Flush all buffers for the active volume in the given range of blocks. | |
@param ulBlockStart Starting block number to flush. | |
@param ulBlockCount Count of blocks, starting at @p ulBlockStart, to flush. | |
Must not be zero. | |
@return A negated ::REDSTATUS code indicating the operation result. | |
@retval 0 Operation was successful. | |
@retval -RED_EIO A disk I/O error occurred. | |
@retval -RED_EINVAL Invalid parameters. | |
*/ | |
REDSTATUS RedBufferFlush( | |
uint32_t ulBlockStart, | |
uint32_t ulBlockCount) | |
{ | |
REDSTATUS ret = 0; | |
if( (ulBlockStart >= gpRedVolume->ulBlockCount) | |
|| ((gpRedVolume->ulBlockCount - ulBlockStart) < ulBlockCount) | |
|| (ulBlockCount == 0U)) | |
{ | |
REDERROR(); | |
ret = -RED_EINVAL; | |
} | |
else | |
{ | |
uint8_t bIdx; | |
for(bIdx = 0U; bIdx < REDCONF_BUFFER_COUNT; bIdx++) | |
{ | |
BUFFERHEAD *pHead = &gBufCtx.aHead[bIdx]; | |
if( (pHead->bVolNum == gbRedVolNum) | |
&& (pHead->ulBlock != BBLK_INVALID) | |
&& ((pHead->uFlags & BFLAG_DIRTY) != 0U) | |
&& (pHead->ulBlock >= ulBlockStart) | |
&& (pHead->ulBlock < (ulBlockStart + ulBlockCount))) | |
{ | |
ret = BufferWrite(bIdx); | |
if(ret == 0) | |
{ | |
pHead->uFlags &= (~BFLAG_DIRTY); | |
} | |
else | |
{ | |
break; | |
} | |
} | |
} | |
} | |
return ret; | |
} | |
/** @brief Mark a buffer dirty | |
@param pBuffer The buffer to mark dirty. | |
*/ | |
void RedBufferDirty( | |
const void *pBuffer) | |
{ | |
uint8_t bIdx; | |
if(!BufferToIdx(pBuffer, &bIdx)) | |
{ | |
REDERROR(); | |
} | |
else | |
{ | |
REDASSERT(gBufCtx.aHead[bIdx].bRefCount > 0U); | |
gBufCtx.aHead[bIdx].uFlags |= BFLAG_DIRTY; | |
} | |
} | |
/** @brief Branch a buffer, marking it dirty and assigning a new block number. | |
@param pBuffer The buffer to branch. | |
@param ulBlockNew The new block number for the buffer. | |
*/ | |
void RedBufferBranch( | |
const void *pBuffer, | |
uint32_t ulBlockNew) | |
{ | |
uint8_t bIdx; | |
if( !BufferToIdx(pBuffer, &bIdx) | |
|| (ulBlockNew >= gpRedVolume->ulBlockCount)) | |
{ | |
REDERROR(); | |
} | |
else | |
{ | |
BUFFERHEAD *pHead = &gBufCtx.aHead[bIdx]; | |
REDASSERT(pHead->bRefCount > 0U); | |
REDASSERT((pHead->uFlags & BFLAG_DIRTY) == 0U); | |
pHead->uFlags |= BFLAG_DIRTY; | |
pHead->ulBlock = ulBlockNew; | |
} | |
} | |
#if (REDCONF_API_POSIX == 1) || FORMAT_SUPPORTED | |
/** @brief Discard a buffer, releasing it and marking it invalid. | |
@param pBuffer The buffer to discard. | |
*/ | |
void RedBufferDiscard( | |
const void *pBuffer) | |
{ | |
uint8_t bIdx; | |
if(!BufferToIdx(pBuffer, &bIdx)) | |
{ | |
REDERROR(); | |
} | |
else | |
{ | |
REDASSERT(gBufCtx.aHead[bIdx].bRefCount == 1U); | |
REDASSERT(gBufCtx.uNumUsed > 0U); | |
gBufCtx.aHead[bIdx].bRefCount = 0U; | |
gBufCtx.aHead[bIdx].ulBlock = BBLK_INVALID; | |
gBufCtx.uNumUsed--; | |
BufferMakeLRU(bIdx); | |
} | |
} | |
#endif | |
#endif /* REDCONF_READ_ONLY == 0 */ | |
/** @brief Discard a range of buffers, marking them invalid. | |
@param ulBlockStart The starting block number to discard | |
@param ulBlockCount The number of blocks, starting at @p ulBlockStart, to | |
discard. Must not be zero. | |
@return A negated ::REDSTATUS code indicating the operation result. | |
@retval 0 Operation was successful. | |
@retval -RED_EINVAL Invalid parameters. | |
@retval -RED_EBUSY A block in the desired range is referenced. | |
*/ | |
REDSTATUS RedBufferDiscardRange( | |
uint32_t ulBlockStart, | |
uint32_t ulBlockCount) | |
{ | |
REDSTATUS ret = 0; | |
if( (ulBlockStart >= gpRedVolume->ulBlockCount) | |
|| ((gpRedVolume->ulBlockCount - ulBlockStart) < ulBlockCount) | |
|| (ulBlockCount == 0U)) | |
{ | |
REDERROR(); | |
ret = -RED_EINVAL; | |
} | |
else | |
{ | |
uint8_t bIdx; | |
for(bIdx = 0U; bIdx < REDCONF_BUFFER_COUNT; bIdx++) | |
{ | |
BUFFERHEAD *pHead = &gBufCtx.aHead[bIdx]; | |
if( (pHead->bVolNum == gbRedVolNum) | |
&& (pHead->ulBlock != BBLK_INVALID) | |
&& (pHead->ulBlock >= ulBlockStart) | |
&& (pHead->ulBlock < (ulBlockStart + ulBlockCount))) | |
{ | |
if(pHead->bRefCount == 0U) | |
{ | |
pHead->ulBlock = BBLK_INVALID; | |
BufferMakeLRU(bIdx); | |
} | |
else | |
{ | |
/* This should never happen. There are three general cases | |
when this function is used: | |
1) Discarding every block, as happens during unmount | |
and at the end of format. There should no longer be | |
any referenced buffers at those points. | |
2) Discarding a block which has become free. All | |
buffers for such blocks should be put or branched | |
beforehand. | |
3) Discarding of blocks that were just written straight | |
to disk, leaving stale data in the buffer. The write | |
code should never reference buffers for these blocks, | |
since they would not be needed or used. | |
*/ | |
CRITICAL_ERROR(); | |
ret = -RED_EBUSY; | |
break; | |
} | |
} | |
} | |
} | |
return ret; | |
} | |
/** Determine whether a metadata buffer is valid. | |
This includes checking its signature, CRC, and sequence number. | |
@param pbBuffer Pointer to the metadata buffer to validate. | |
@param uFlags The buffer flags provided by the caller. Used to determine | |
the expected signature. | |
@return Whether the metadata buffer is valid. | |
@retval true The metadata buffer is valid. | |
@retval false The metadata buffer is invalid. | |
*/ | |
static bool BufferIsValid( | |
const uint8_t *pbBuffer, | |
uint16_t uFlags) | |
{ | |
bool fValid; | |
if((pbBuffer == NULL) || ((uFlags & BFLAG_MASK) != uFlags)) | |
{ | |
REDERROR(); | |
fValid = false; | |
} | |
else | |
{ | |
NODEHEADER buf; | |
uint16_t uMetaFlags = uFlags & BFLAG_META_MASK; | |
/* Casting pbBuffer to (NODEHEADER *) would run afoul MISRA-C:2012 | |
R11.3, so instead copy the fields out. | |
*/ | |
RedMemCpy(&buf.ulSignature, &pbBuffer[NODEHEADER_OFFSET_SIG], sizeof(buf.ulSignature)); | |
RedMemCpy(&buf.ulCRC, &pbBuffer[NODEHEADER_OFFSET_CRC], sizeof(buf.ulCRC)); | |
RedMemCpy(&buf.ullSequence, &pbBuffer[NODEHEADER_OFFSET_SEQ], sizeof(buf.ullSequence)); | |
#ifdef REDCONF_ENDIAN_SWAP | |
buf.ulCRC = RedRev32(buf.ulCRC); | |
buf.ulSignature = RedRev32(buf.ulSignature); | |
buf.ullSequence = RedRev64(buf.ullSequence); | |
#endif | |
/* Make sure the signature is correct for the type of metadata block | |
requested by the caller. | |
*/ | |
switch(buf.ulSignature) | |
{ | |
case META_SIG_MASTER: | |
fValid = (uMetaFlags == BFLAG_META_MASTER); | |
break; | |
#if REDCONF_IMAP_EXTERNAL == 1 | |
case META_SIG_IMAP: | |
fValid = (uMetaFlags == BFLAG_META_IMAP); | |
break; | |
#endif | |
case META_SIG_INODE: | |
fValid = (uMetaFlags == BFLAG_META_INODE); | |
break; | |
#if DINDIR_POINTERS > 0U | |
case META_SIG_DINDIR: | |
fValid = (uMetaFlags == BFLAG_META_DINDIR); | |
break; | |
#endif | |
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES | |
case META_SIG_INDIR: | |
fValid = (uMetaFlags == BFLAG_META_INDIR); | |
break; | |
#endif | |
default: | |
fValid = false; | |
break; | |
} | |
if(fValid) | |
{ | |
uint32_t ulComputedCrc; | |
/* Check for disk corruption by comparing the stored CRC with one | |
computed from the data. | |
Also check the sequence number: if it is greater than the | |
current sequence number, the block is from a previous format | |
or the disk is writing blocks out of order. During mount, | |
before the metaroots have been read, the sequence number will | |
be unknown, and the check is skipped. | |
*/ | |
ulComputedCrc = RedCrcNode(pbBuffer); | |
if(buf.ulCRC != ulComputedCrc) | |
{ | |
fValid = false; | |
} | |
else if(gpRedVolume->fMounted && (buf.ullSequence >= gpRedVolume->ullSequence)) | |
{ | |
fValid = false; | |
} | |
else | |
{ | |
/* Buffer is valid. No action, fValid is already true. | |
*/ | |
} | |
} | |
} | |
return fValid; | |
} | |
/** @brief Derive the index of the buffer. | |
@param pBuffer The buffer to derive the index of. | |
@param pbIdx On success, populated with the index of the buffer. | |
@return Boolean indicating result. | |
@retval true Success. | |
@retval false Failure. @p pBuffer is not a valid buffer pointer. | |
*/ | |
static bool BufferToIdx( | |
const void *pBuffer, | |
uint8_t *pbIdx) | |
{ | |
bool fRet = false; | |
if((pBuffer != NULL) && (pbIdx != NULL)) | |
{ | |
uint8_t bIdx; | |
/* pBuffer should be a pointer to one of the block buffers. | |
A good compiler should optimize this loop into a bounds check and an | |
alignment check, although GCC has been observed to not do so; if the | |
number of buffers is small, it should not make much difference. The | |
alternative is to use pointer comparisons, but this both deviates | |
from MISRA-C:2012 and involves undefined behavior. | |
*/ | |
for(bIdx = 0U; bIdx < REDCONF_BUFFER_COUNT; bIdx++) | |
{ | |
if(pBuffer == &gBufCtx.b.aabBuffer[bIdx][0U]) | |
{ | |
break; | |
} | |
} | |
if( (bIdx < REDCONF_BUFFER_COUNT) | |
&& (gBufCtx.aHead[bIdx].ulBlock != BBLK_INVALID) | |
&& (gBufCtx.aHead[bIdx].bVolNum == gbRedVolNum)) | |
{ | |
*pbIdx = bIdx; | |
fRet = true; | |
} | |
} | |
return fRet; | |
} | |
#if REDCONF_READ_ONLY == 0 | |
/** @brief Write out a dirty buffer. | |
@param bIdx The index of the buffer to write. | |
@return A negated ::REDSTATUS code indicating the operation result. | |
@retval 0 Operation was successful. | |
@retval -RED_EIO A disk I/O error occurred. | |
@retval -RED_EINVAL Invalid parameters. | |
*/ | |
static REDSTATUS BufferWrite( | |
uint8_t bIdx) | |
{ | |
REDSTATUS ret = 0; | |
if(bIdx < REDCONF_BUFFER_COUNT) | |
{ | |
const BUFFERHEAD *pHead = &gBufCtx.aHead[bIdx]; | |
REDASSERT((pHead->uFlags & BFLAG_DIRTY) != 0U); | |
if((pHead->uFlags & BFLAG_META) != 0U) | |
{ | |
ret = BufferFinalize(gBufCtx.b.aabBuffer[bIdx], pHead->uFlags); | |
} | |
if(ret == 0) | |
{ | |
ret = RedIoWrite(pHead->bVolNum, pHead->ulBlock, 1U, gBufCtx.b.aabBuffer[bIdx]); | |
#ifdef REDCONF_ENDIAN_SWAP | |
BufferEndianSwap(gBufCtx.b.aabBuffer[bIdx], pHead->uFlags); | |
#endif | |
} | |
} | |
else | |
{ | |
REDERROR(); | |
ret = -RED_EINVAL; | |
} | |
return ret; | |
} | |
/** @brief Finalize a metadata buffer. | |
This updates the CRC and the sequence number. It also sets the signature, | |
though this is only truly needed if the buffer is new. | |
@param pbBuffer Pointer to the metadata buffer to finalize. | |
@param uFlags The associated buffer flags. Used to determine the expected | |
signature. | |
@return A negated ::REDSTATUS code indicating the operation result. | |
@retval 0 Operation was successful. | |
@retval -RED_EINVAL Invalid parameter; or maximum sequence number reached. | |
*/ | |
static REDSTATUS BufferFinalize( | |
uint8_t *pbBuffer, | |
uint16_t uFlags) | |
{ | |
REDSTATUS ret = 0; | |
if((pbBuffer == NULL) || ((uFlags & BFLAG_MASK) != uFlags)) | |
{ | |
REDERROR(); | |
ret = -RED_EINVAL; | |
} | |
else | |
{ | |
uint32_t ulSignature; | |
switch(uFlags & BFLAG_META_MASK) | |
{ | |
case BFLAG_META_MASTER: | |
ulSignature = META_SIG_MASTER; | |
break; | |
#if REDCONF_IMAP_EXTERNAL == 1 | |
case BFLAG_META_IMAP: | |
ulSignature = META_SIG_IMAP; | |
break; | |
#endif | |
case BFLAG_META_INODE: | |
ulSignature = META_SIG_INODE; | |
break; | |
#if DINDIR_POINTERS > 0U | |
case BFLAG_META_DINDIR: | |
ulSignature = META_SIG_DINDIR; | |
break; | |
#endif | |
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES | |
case BFLAG_META_INDIR: | |
ulSignature = META_SIG_INDIR; | |
break; | |
#endif | |
default: | |
ulSignature = 0U; | |
break; | |
} | |
if(ulSignature == 0U) | |
{ | |
REDERROR(); | |
ret = -RED_EINVAL; | |
} | |
else | |
{ | |
uint64_t ullSeqNum = gpRedVolume->ullSequence; | |
ret = RedVolSeqNumIncrement(); | |
if(ret == 0) | |
{ | |
uint32_t ulCrc; | |
RedMemCpy(&pbBuffer[NODEHEADER_OFFSET_SIG], &ulSignature, sizeof(ulSignature)); | |
RedMemCpy(&pbBuffer[NODEHEADER_OFFSET_SEQ], &ullSeqNum, sizeof(ullSeqNum)); | |
#ifdef REDCONF_ENDIAN_SWAP | |
BufferEndianSwap(pbBuffer, uFlags); | |
#endif | |
ulCrc = RedCrcNode(pbBuffer); | |
#ifdef REDCONF_ENDIAN_SWAP | |
ulCrc = RedRev32(ulCrc); | |
#endif | |
RedMemCpy(&pbBuffer[NODEHEADER_OFFSET_CRC], &ulCrc, sizeof(ulCrc)); | |
} | |
} | |
} | |
return ret; | |
} | |
#endif /* REDCONF_READ_ONLY == 0 */ | |
#ifdef REDCONF_ENDIAN_SWAP | |
/** @brief Swap the byte order of a metadata buffer | |
Does nothing if the buffer is not a metadata node. Also does nothing for | |
meta roots, which don't go through the buffers anyways. | |
@param pBuffer Pointer to the metadata buffer to swap | |
@param uFlags The associated buffer flags. Used to determin the type of | |
metadata node. | |
*/ | |
static void BufferEndianSwap( | |
void *pBuffer, | |
uint16_t uFlags) | |
{ | |
if((pBuffer == NULL) || ((uFlags & BFLAG_MASK) != uFlags)) | |
{ | |
REDERROR(); | |
} | |
else if((uFlags & BFLAG_META_MASK) != 0) | |
{ | |
BufferEndianSwapHeader(pBuffer); | |
switch(uFlags & BFLAG_META_MASK) | |
{ | |
case BFLAG_META_MASTER: | |
BufferEndianSwapMaster(pBuffer); | |
break; | |
case BFLAG_META_INODE: | |
BufferEndianSwapInode(pBuffer); | |
break; | |
#if DINDIR_POINTERS > 0U | |
case BFLAG_META_DINDIR: | |
BufferEndianSwapIndir(pBuffer); | |
break; | |
#endif | |
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES | |
case BFLAG_META_INDIR: | |
BufferEndianSwapIndir(pBuffer); | |
break; | |
#endif | |
default: | |
break; | |
} | |
} | |
else | |
{ | |
/* File data buffers do not need to be swapped. | |
*/ | |
} | |
} | |
/** @brief Swap the byte order of a metadata node header | |
@param pHeader Pointer to the metadata node header to swap | |
*/ | |
static void BufferEndianSwapHeader( | |
NODEHEADER *pHeader) | |
{ | |
if(pHeader == NULL) | |
{ | |
REDERROR(); | |
} | |
else | |
{ | |
pHeader->ulSignature = RedRev32(pHeader->ulSignature); | |
pHeader->ulCRC = RedRev32(pHeader->ulCRC); | |
pHeader->ullSequence = RedRev64(pHeader->ullSequence); | |
} | |
} | |
/** @brief Swap the byte order of a master block | |
@param pMaster Pointer to the master block to swap | |
*/ | |
static void BufferEndianSwapMaster( | |
MASTERBLOCK *pMaster) | |
{ | |
if(pMaster == NULL) | |
{ | |
REDERROR(); | |
} | |
else | |
{ | |
pMaster->ulVersion = RedRev32(pMaster->ulVersion); | |
pMaster->ulFormatTime = RedRev32(pMaster->ulFormatTime); | |
pMaster->ulInodeCount = RedRev32(pMaster->ulInodeCount); | |
pMaster->ulBlockCount = RedRev32(pMaster->ulBlockCount); | |
pMaster->uMaxNameLen = RedRev16(pMaster->uMaxNameLen); | |
pMaster->uDirectPointers = RedRev16(pMaster->uDirectPointers); | |
pMaster->uIndirectPointers = RedRev16(pMaster->uIndirectPointers); | |
} | |
} | |
/** @brief Swap the byte order of an inode | |
@param pInode Pointer to the inode to swap | |
*/ | |
static void BufferEndianSwapInode( | |
INODE *pInode) | |
{ | |
if(pInode == NULL) | |
{ | |
REDERROR(); | |
} | |
else | |
{ | |
uint32_t ulIdx; | |
pInode->ullSize = RedRev64(pInode->ullSize); | |
#if REDCONF_INODE_BLOCKS == 1 | |
pInode->ulBlocks = RedRev32(pInode->ulBlocks); | |
#endif | |
#if REDCONF_INODE_TIMESTAMPS == 1 | |
pInode->ulATime = RedRev32(pInode->ulATime); | |
pInode->ulMTime = RedRev32(pInode->ulMTime); | |
pInode->ulCTime = RedRev32(pInode->ulCTime); | |
#endif | |
pInode->uMode = RedRev16(pInode->uMode); | |
#if (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_LINK == 1) | |
pInode->uNLink = RedRev16(pInode->uNLink); | |
#endif | |
#if REDCONF_API_POSIX == 1 | |
pInode->ulPInode = RedRev32(pInode->ulPInode); | |
#endif | |
for(ulIdx = 0; ulIdx < INODE_ENTRIES; ulIdx++) | |
{ | |
pInode->aulEntries[ulIdx] = RedRev32(pInode->aulEntries[ulIdx]); | |
} | |
} | |
} | |
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES | |
/** @brief Swap the byte order of an indirect or double indirect node | |
@param pIndir Pointer to the node to swap | |
*/ | |
static void BufferEndianSwapIndir( | |
INDIR *pIndir) | |
{ | |
if(pIndir == NULL) | |
{ | |
REDERROR(); | |
} | |
else | |
{ | |
uint32_t ulIdx; | |
pIndir->ulInode = RedRev32(pIndir->ulInode); | |
for(ulIdx = 0; ulIdx < INDIR_ENTRIES; ulIdx++) | |
{ | |
pIndir->aulEntries[ulIdx] = RedRev32(pIndir->aulEntries[ulIdx]); | |
} | |
} | |
} | |
#endif /* REDCONF_DIRECT_POINTERS < INODE_ENTRIES */ | |
#endif /* #ifdef REDCONF_ENDIAN_SWAP */ | |
/** @brief Mark a buffer as least recently used. | |
@param bIdx The index of the buffer to make LRU. | |
*/ | |
static void BufferMakeLRU( | |
uint8_t bIdx) | |
{ | |
if(bIdx >= REDCONF_BUFFER_COUNT) | |
{ | |
REDERROR(); | |
} | |
else if(bIdx != gBufCtx.abMRU[REDCONF_BUFFER_COUNT - 1U]) | |
{ | |
uint8_t bMruIdx; | |
/* Find the current position of the buffer in the MRU array. We do not | |
need to check the last slot, since we already know from the above | |
check that the index is not there. | |
*/ | |
for(bMruIdx = 0U; bMruIdx < (REDCONF_BUFFER_COUNT - 1U); bMruIdx++) | |
{ | |
if(bIdx == gBufCtx.abMRU[bMruIdx]) | |
{ | |
break; | |
} | |
} | |
if(bMruIdx < (REDCONF_BUFFER_COUNT - 1U)) | |
{ | |
/* Move the buffer index to the back of the MRU array, making it | |
the LRU buffer. | |
*/ | |
RedMemMove(&gBufCtx.abMRU[bMruIdx], &gBufCtx.abMRU[bMruIdx + 1U], REDCONF_BUFFER_COUNT - ((uint32_t)bMruIdx + 1U)); | |
gBufCtx.abMRU[REDCONF_BUFFER_COUNT - 1U] = bIdx; | |
} | |
else | |
{ | |
REDERROR(); | |
} | |
} | |
else | |
{ | |
/* Buffer already LRU, nothing to do. | |
*/ | |
} | |
} | |
/** @brief Mark a buffer as most recently used. | |
@param bIdx The index of the buffer to make MRU. | |
*/ | |
static void BufferMakeMRU( | |
uint8_t bIdx) | |
{ | |
if(bIdx >= REDCONF_BUFFER_COUNT) | |
{ | |
REDERROR(); | |
} | |
else if(bIdx != gBufCtx.abMRU[0U]) | |
{ | |
uint8_t bMruIdx; | |
/* Find the current position of the buffer in the MRU array. We do not | |
need to check the first slot, since we already know from the above | |
check that the index is not there. | |
*/ | |
for(bMruIdx = 1U; bMruIdx < REDCONF_BUFFER_COUNT; bMruIdx++) | |
{ | |
if(bIdx == gBufCtx.abMRU[bMruIdx]) | |
{ | |
break; | |
} | |
} | |
if(bMruIdx < REDCONF_BUFFER_COUNT) | |
{ | |
/* Move the buffer index to the front of the MRU array, making it | |
the MRU buffer. | |
*/ | |
RedMemMove(&gBufCtx.abMRU[1U], &gBufCtx.abMRU[0U], bMruIdx); | |
gBufCtx.abMRU[0U] = bIdx; | |
} | |
else | |
{ | |
REDERROR(); | |
} | |
} | |
else | |
{ | |
/* Buffer already MRU, nothing to do. | |
*/ | |
} | |
} | |
/** @brief Find a block in the buffers. | |
@param ulBlock The block number to find. | |
@param pbIdx If the block is buffered (true is returned), populated with | |
the index of the buffer. | |
@return Boolean indicating whether or not the block is buffered. | |
@retval true @p ulBlock is buffered, and its index has been stored in | |
@p pbIdx. | |
@retval false @p ulBlock is not buffered. | |
*/ | |
static bool BufferFind( | |
uint32_t ulBlock, | |
uint8_t *pbIdx) | |
{ | |
bool ret = false; | |
if((ulBlock >= gpRedVolume->ulBlockCount) || (pbIdx == NULL)) | |
{ | |
REDERROR(); | |
} | |
else | |
{ | |
uint8_t bIdx; | |
for(bIdx = 0U; bIdx < REDCONF_BUFFER_COUNT; bIdx++) | |
{ | |
const BUFFERHEAD *pHead = &gBufCtx.aHead[bIdx]; | |
if((pHead->bVolNum == gbRedVolNum) && (pHead->ulBlock == ulBlock)) | |
{ | |
*pbIdx = bIdx; | |
ret = true; | |
break; | |
} | |
} | |
} | |
return ret; | |
} | |