mirror of
https://github.com/FreeRTOS/FreeRTOS-Kernel.git
synced 2025-04-21 22:11:57 -04:00
1213 lines
36 KiB
C
1213 lines
36 KiB
C
/* ----> 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;
|
|
}
|
|
|