blob: 17717e0f4bb8cbb5c144e04a61d612e66d3f34e1 [file] [log] [blame]
/* ----> 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) == BFLAG_META_MASTER);
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;
}