Files
Soren Ptak 3a2f6646f0 Use CI-CD-Github-Actions for spelling and formatting, add in the bot formatting action, update the CI-CD workflow files. Fix incorrect spelling and formatting on files. (#1083)
* Use new version of CI-CD Actions,  checkout@v3 instead of checkout@v2 on all jobs
* Use cSpell spell check, and use ubuntu-20.04 for formatting check
* Add in bot formatting action
* Update freertos_demo.yml and freertos_plus_demo.yml files to increase github log readability
* Add in a Qemu demo onto the workflows.
2023-09-06 12:35:37 -07:00

1217 lines
38 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 /* if REDCONF_API_POSIX_RENAME == 1 */
#endif /* if ( REDCONF_READ_ONLY == 1 ) || ( REDCONF_API_FSE == 1 ) */
#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 /* if ( REDCONF_API_POSIX == 1 ) || FORMAT_SUPPORTED */
#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;
}