FreeRTOS-Kernel/FreeRTOS-Plus/Source/Reliance-Edge/core/driver/inodedata.c
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

1936 lines
68 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 inode I/O functions.
*/
#include <redfs.h>
#include <redcore.h>
/* This value is used to initialize the uIndirEntry and uDindirEntry members of
* the CINODE structure. After seeking, a value of COORD_ENTRY_INVALID in
* uIndirEntry indicates that there is no indirect node in the path through the
* file metadata structure, and a value of COORD_ENTRY_INVALID in uDindirEntry
* indicates that there is no double indirect node.
*/
#define COORD_ENTRY_INVALID ( UINT16_MAX )
/* This enumeration is used by the BranchBlock() and BranchBlockCost()
* functions to determine which blocks of the file metadata structure need to
* be branched, and which to ignore. DINDIR requires requires branching the
* double indirect only, INDIR requires branching the double indirect
* (if present) and the indirect, and FILE_DATA requires branching the indirect
* and double indirect (if present) and the file data block.
*/
typedef enum
{
BRANCHDEPTH_DINDIR = 0U,
BRANCHDEPTH_INDIR = 1U,
BRANCHDEPTH_FILE_DATA = 2U,
BRANCHDEPTH_MAX = BRANCHDEPTH_FILE_DATA
} BRANCHDEPTH;
#if REDCONF_READ_ONLY == 0
#if DELETE_SUPPORTED || TRUNCATE_SUPPORTED
static REDSTATUS Shrink( CINODE * pInode,
uint64_t ullSize );
#if DINDIR_POINTERS > 0U
static REDSTATUS TruncDindir( CINODE * pInode,
bool * pfFreed );
#endif
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
static REDSTATUS TruncIndir( CINODE * pInode,
bool * pfFreed );
#endif
static REDSTATUS TruncDataBlock( const CINODE * pInode,
uint32_t * pulBlock,
bool fPropagate );
#endif /* if DELETE_SUPPORTED || TRUNCATE_SUPPORTED */
static REDSTATUS ExpandPrepare( CINODE * pInode );
#endif /* if REDCONF_READ_ONLY == 0 */
static void SeekCoord( CINODE * pInode,
uint32_t ulBlock );
static REDSTATUS ReadUnaligned( CINODE * pInode,
uint64_t ullStart,
uint32_t ulLen,
uint8_t * pbBuffer );
static REDSTATUS ReadAligned( CINODE * pInode,
uint32_t ulBlockStart,
uint32_t ulBlockCount,
uint8_t * pbBuffer );
#if REDCONF_READ_ONLY == 0
static REDSTATUS WriteUnaligned( CINODE * pInode,
uint64_t ullStart,
uint32_t ulLen,
const uint8_t * pbBuffer );
static REDSTATUS WriteAligned( CINODE * pInode,
uint32_t ulBlockStart,
uint32_t * pulBlockCount,
const uint8_t * pbBuffer );
#endif
static REDSTATUS GetExtent( CINODE * pInode,
uint32_t ulBlockStart,
uint32_t * pulExtentStart,
uint32_t * pulExtentLen );
#if REDCONF_READ_ONLY == 0
static REDSTATUS BranchBlock( CINODE * pInode,
BRANCHDEPTH depth,
bool fBuffer );
static REDSTATUS BranchOneBlock( uint32_t * pulBlock,
void ** ppBuffer,
uint16_t uBFlag );
static REDSTATUS BranchBlockCost( const CINODE * pInode,
BRANCHDEPTH depth,
uint32_t * pulCost );
static uint32_t FreeBlockCount( void );
#endif /* if REDCONF_READ_ONLY == 0 */
/** @brief Read data from an inode.
*
* @param pInode A pointer to the cached inode structure of the inode from
* which to read.
* @param ullStart The file offset at which to read.
* @param pulLen On input, the number of bytes to attempt to read. On
* successful return, populated with the number of bytes
* actually read.
* @param pBuffer The buffer to read into.
*
* @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 @p pInode is not a mounted cached inode pointer; or
* @p pulLen is `NULL`; or @p pBuffer is `NULL`.
*/
REDSTATUS RedInodeDataRead( CINODE * pInode,
uint64_t ullStart,
uint32_t * pulLen,
void * pBuffer )
{
REDSTATUS ret = 0;
if( !CINODE_IS_MOUNTED( pInode ) || ( pulLen == NULL ) || ( pBuffer == NULL ) )
{
ret = -RED_EINVAL;
}
else if( ullStart >= pInode->pInodeBuf->ullSize )
{
*pulLen = 0U;
}
else if( *pulLen == 0U )
{
/* Do nothing, just return success.
*/
}
else
{
uint8_t * pbBuffer = CAST_VOID_PTR_TO_UINT8_PTR( pBuffer );
uint32_t ulReadIndex = 0U;
uint32_t ulLen = *pulLen;
uint32_t ulRemaining;
/* Reading beyond the end of the file is not allowed. If the requested
* read extends beyond the end of the file, truncate the read length so
* that the read stops at the end of the file.
*/
if( ( pInode->pInodeBuf->ullSize - ullStart ) < ulLen )
{
ulLen = ( uint32_t ) ( pInode->pInodeBuf->ullSize - ullStart );
}
ulRemaining = ulLen;
/* Unaligned partial block at start.
*/
if( ( ullStart & ( REDCONF_BLOCK_SIZE - 1U ) ) != 0U )
{
uint32_t ulBytesInFirstBlock = REDCONF_BLOCK_SIZE - ( uint32_t ) ( ullStart & ( REDCONF_BLOCK_SIZE - 1U ) );
uint32_t ulThisRead = REDMIN( ulRemaining, ulBytesInFirstBlock );
ret = ReadUnaligned( pInode, ullStart, ulThisRead, pbBuffer );
if( ret == 0 )
{
ulReadIndex += ulThisRead;
ulRemaining -= ulThisRead;
}
}
/* Whole blocks.
*/
if( ( ret == 0 ) && ( ulRemaining >= REDCONF_BLOCK_SIZE ) )
{
uint32_t ulBlockOffset = ( uint32_t ) ( ( ullStart + ulReadIndex ) >> BLOCK_SIZE_P2 );
uint32_t ulBlockCount = ulRemaining >> BLOCK_SIZE_P2;
REDASSERT( ( ( ullStart + ulReadIndex ) & ( REDCONF_BLOCK_SIZE - 1U ) ) == 0U );
ret = ReadAligned( pInode, ulBlockOffset, ulBlockCount, &pbBuffer[ ulReadIndex ] );
if( ret == 0 )
{
ulReadIndex += ulBlockCount << BLOCK_SIZE_P2;
ulRemaining -= ulBlockCount << BLOCK_SIZE_P2;
}
}
/* Aligned partial block at end.
*/
if( ( ret == 0 ) && ( ulRemaining > 0U ) )
{
REDASSERT( ulRemaining < REDCONF_BLOCK_SIZE );
REDASSERT( ( ( ullStart + ulReadIndex ) & ( REDCONF_BLOCK_SIZE - 1U ) ) == 0U );
ret = ReadUnaligned( pInode, ullStart + ulReadIndex, ulRemaining, &pbBuffer[ ulReadIndex ] );
}
if( ret == 0 )
{
*pulLen = ulLen;
}
}
return ret;
}
#if REDCONF_READ_ONLY == 0
/** @brief Write to an inode.
*
* @param pInode A pointer to the cached inode structure of the inode into
* which to write.
* @param ullStart The file offset at which to write.
* @param pulLen On input, the number of bytes to attempt to write. On
* successful return, populated with the number of bytes
* actually written.
* @param pBuffer The buffer to write from.
*
* @return A negated ::REDSTATUS code indicating the operation result.
*
* @retval 0 Operation was successful.
* @retval -RED_EFBIG @p ullStart is greater than the maximum file size; or
* @p ullStart is equal to the maximum file size and the
* write length is non-zero.
* @retval -RED_EINVAL @p pInode is not a mounted cached inode pointer; or
* @p pulLen is `NULL`; or @p pBuffer is `NULL`.
* @retval -RED_EIO A disk I/O error occurred.
* @retval -RED_ENOSPC No data can be written because there is insufficient
* free space.
*/
REDSTATUS RedInodeDataWrite( CINODE * pInode,
uint64_t ullStart,
uint32_t * pulLen,
const void * pBuffer )
{
REDSTATUS ret = 0;
if( !CINODE_IS_DIRTY( pInode ) || ( pulLen == NULL ) || ( pBuffer == NULL ) )
{
ret = -RED_EINVAL;
}
else if( ( ullStart > INODE_SIZE_MAX ) || ( ( ullStart == INODE_SIZE_MAX ) && ( *pulLen > 0U ) ) )
{
ret = -RED_EFBIG;
}
else if( *pulLen == 0U )
{
/* Do nothing, just return success.
*/
}
else
{
const uint8_t * pbBuffer = CAST_VOID_PTR_TO_CONST_UINT8_PTR( pBuffer );
uint32_t ulWriteIndex = 0U;
uint32_t ulLen = *pulLen;
uint32_t ulRemaining;
if( ( INODE_SIZE_MAX - ullStart ) < ulLen )
{
ulLen = ( uint32_t ) ( INODE_SIZE_MAX - ullStart );
}
ulRemaining = ulLen;
/* If the write is beyond the current end of the file, and the current
* end of the file is not block-aligned, then there may be some data
* that needs to be zeroed in the last block.
*/
if( ullStart > pInode->pInodeBuf->ullSize )
{
ret = ExpandPrepare( pInode );
}
/* Partial block at start.
*/
if( ( ret == 0 ) && ( ( ( ullStart & ( REDCONF_BLOCK_SIZE - 1U ) ) != 0U ) || ( ulRemaining < REDCONF_BLOCK_SIZE ) ) )
{
uint32_t ulBytesInFirstBlock = REDCONF_BLOCK_SIZE - ( uint32_t ) ( ullStart & ( REDCONF_BLOCK_SIZE - 1U ) );
uint32_t ulThisWrite = REDMIN( ulRemaining, ulBytesInFirstBlock );
ret = WriteUnaligned( pInode, ullStart, ulThisWrite, pbBuffer );
if( ret == 0 )
{
ulWriteIndex += ulThisWrite;
ulRemaining -= ulThisWrite;
}
}
/* Whole blocks.
*/
if( ( ret == 0 ) && ( ulRemaining >= REDCONF_BLOCK_SIZE ) )
{
uint32_t ulBlockOffset = ( uint32_t ) ( ( ullStart + ulWriteIndex ) >> BLOCK_SIZE_P2 );
uint32_t ulBlockCount = ulRemaining >> BLOCK_SIZE_P2;
uint32_t ulBlocksWritten = ulBlockCount;
REDASSERT( ( ( ullStart + ulWriteIndex ) & ( REDCONF_BLOCK_SIZE - 1U ) ) == 0U );
ret = WriteAligned( pInode, ulBlockOffset, &ulBlocksWritten, &pbBuffer[ ulWriteIndex ] );
if( ( ret == -RED_ENOSPC ) && ( ulWriteIndex > 0U ) )
{
ulBlocksWritten = 0U;
ret = 0;
}
if( ret == 0 )
{
ulWriteIndex += ulBlocksWritten << BLOCK_SIZE_P2;
ulRemaining -= ulBlocksWritten << BLOCK_SIZE_P2;
if( ulBlocksWritten < ulBlockCount )
{
ulRemaining = 0U;
}
}
}
/* Partial block at end.
*/
if( ( ret == 0 ) && ( ulRemaining > 0U ) )
{
REDASSERT( ulRemaining < REDCONF_BLOCK_SIZE );
REDASSERT( ( ( ullStart + ulWriteIndex ) & ( REDCONF_BLOCK_SIZE - 1U ) ) == 0U );
REDASSERT( ulWriteIndex > 0U );
ret = WriteUnaligned( pInode, ullStart + ulWriteIndex, ulRemaining, &pbBuffer[ ulWriteIndex ] );
if( ret == -RED_ENOSPC )
{
ret = 0;
}
else if( ret == 0 )
{
ulWriteIndex += ulRemaining;
REDASSERT( ulWriteIndex == ulLen );
}
else
{
/* Unexpected error, return it.
*/
}
}
if( ret == 0 )
{
*pulLen = ulWriteIndex;
if( ( ullStart + ulWriteIndex ) > pInode->pInodeBuf->ullSize )
{
pInode->pInodeBuf->ullSize = ullStart + ulWriteIndex;
}
}
}
return ret;
}
#if DELETE_SUPPORTED || TRUNCATE_SUPPORTED
/** @brief Change the size of an inode.
*
* @param pInode A pointer to the cached inode structure.
* @param ullSize The new file size for the inode.
*
* @return A negated ::REDSTATUS code indicating the operation result.
*
* @retval 0 Operation was successful.
* @retval -RED_EFBIG @p ullSize is greater than the maximum file size.
* @retval -RED_EINVAL @p pInode is not a mounted cached inode pointer.
* @retval -RED_EIO A disk I/O error occurred.
* @retval -RED_ENOSPC Insufficient free space to perform the truncate.
*/
REDSTATUS RedInodeDataTruncate( CINODE * pInode,
uint64_t ullSize )
{
REDSTATUS ret = 0;
/* The inode does not need to be dirtied when it is being deleted, because
* the inode buffer will be discarded without ever being written to disk.
* Thus, we only check to see if it's mounted here.
*/
if( !CINODE_IS_MOUNTED( pInode ) )
{
ret = -RED_EINVAL;
}
else if( ullSize > INODE_SIZE_MAX )
{
ret = -RED_EFBIG;
}
else
{
if( ullSize > pInode->pInodeBuf->ullSize )
{
ret = ExpandPrepare( pInode );
}
else if( ullSize < pInode->pInodeBuf->ullSize )
{
ret = Shrink( pInode, ullSize );
}
else
{
/* Size is staying the same, nothing to do.
*/
}
if( ret == 0 )
{
pInode->pInodeBuf->ullSize = ullSize;
}
}
return ret;
}
/** @brief Free all file data beyond a specified point.
*
* @param pInode A pointer to the cached inode structure.
* @param ullSize The point beyond which to free all file data.
*
* @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_ENOSPC Insufficient free space to perform the truncate.
* @retval -RED_EINVAL Invalid parameters.
*/
static REDSTATUS Shrink( CINODE * pInode,
uint64_t ullSize )
{
REDSTATUS ret = 0;
/* pInode->fDirty is checked explicitly here, instead of using the
* CINODE_IS_DIRTY() macro, to avoid a duplicate mount check.
*/
if( !CINODE_IS_MOUNTED( pInode ) || ( ( ullSize > 0U ) && !pInode->fDirty ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
uint32_t ulTruncBlock = ( uint32_t ) ( ( ullSize + REDCONF_BLOCK_SIZE - 1U ) >> BLOCK_SIZE_P2 );
RedInodePutData( pInode );
#if REDCONF_DIRECT_POINTERS > 0U
while( ulTruncBlock < REDCONF_DIRECT_POINTERS )
{
ret = TruncDataBlock( pInode, &pInode->pInodeBuf->aulEntries[ ulTruncBlock ], true );
if( ret != 0 )
{
break;
}
ulTruncBlock++;
}
#endif /* if REDCONF_DIRECT_POINTERS > 0U */
#if REDCONF_INDIRECT_POINTERS > 0U
while( ( ret == 0 ) && ( ulTruncBlock < ( REDCONF_DIRECT_POINTERS + INODE_INDIR_BLOCKS ) ) )
{
ret = RedInodeDataSeek( pInode, ulTruncBlock );
if( ( ret == 0 ) || ( ret == -RED_ENODATA ) )
{
bool fFreed;
ret = TruncIndir( pInode, &fFreed );
if( ret == 0 )
{
if( fFreed )
{
pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ] = BLOCK_SPARSE;
}
/* The next seek will go to the beginning of the next
* indirect.
*/
ulTruncBlock += ( INDIR_ENTRIES - pInode->uIndirEntry );
}
}
}
#endif /* if REDCONF_INDIRECT_POINTERS > 0U */
#if DINDIR_POINTERS > 0U
while( ( ret == 0 ) && ( ulTruncBlock < INODE_DATA_BLOCKS ) )
{
ret = RedInodeDataSeek( pInode, ulTruncBlock );
if( ( ret == 0 ) || ( ret == -RED_ENODATA ) )
{
bool fFreed;
/* TruncDindir() invokes seek as it goes along, which will
* update the entry values (possibly all three of these);
* make a copy so we can compute things correctly after.
*/
uint16_t uOrigInodeEntry = pInode->uInodeEntry;
uint16_t uOrigDindirEntry = pInode->uDindirEntry;
uint16_t uOrigIndirEntry = pInode->uIndirEntry;
ret = TruncDindir( pInode, &fFreed );
if( ret == 0 )
{
if( fFreed )
{
pInode->pInodeBuf->aulEntries[ uOrigInodeEntry ] = BLOCK_SPARSE;
}
/* The next seek will go to the beginning of the next
* double indirect.
*/
ulTruncBlock += ( DINDIR_DATA_BLOCKS - ( uOrigDindirEntry * INDIR_ENTRIES ) ) - uOrigIndirEntry;
}
}
}
#endif /* if DINDIR_POINTERS > 0U */
}
return ret;
}
#if DINDIR_POINTERS > 0U
/** @brief Truncate a double indirect.
*
* @param pInode A pointer to the cached inode, whose coordinates indicate
* the truncation boundary.
* @param pfFreed On successful return, populated with whether the double
* indirect node was freed.
*
* @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_ENOSPC Insufficient free space to perform the truncate.
* @retval -RED_EINVAL Invalid parameters.
*/
static REDSTATUS TruncDindir( CINODE * pInode,
bool * pfFreed )
{
REDSTATUS ret = 0;
if( !CINODE_IS_MOUNTED( pInode ) || ( pfFreed == NULL ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else if( pInode->pDindir == NULL )
{
*pfFreed = false;
}
else
{
bool fBranch = false;
uint16_t uEntry;
/* The double indirect is definitely going to be branched (instead of
* deleted) if any of its indirect pointers which are entirely prior to
* the truncation boundary are non-sparse.
*/
for( uEntry = 0U; !fBranch && ( uEntry < pInode->uDindirEntry ); uEntry++ )
{
fBranch = pInode->pDindir->aulEntries[ uEntry ] != BLOCK_SPARSE;
}
/* Unless we already know for a fact that the double indirect is going
* to be branched, examine the contents of the indirect pointer which
* straddles the truncation boundary. If the indirect is going to be
* deleted, we know this indirect pointer is going away, and that might
* mean the double indirect is going to be deleted also.
*/
if( !fBranch && ( pInode->pDindir->aulEntries[ pInode->uDindirEntry ] != BLOCK_SPARSE ) )
{
for( uEntry = 0U; !fBranch && ( uEntry < pInode->uIndirEntry ); uEntry++ )
{
fBranch = pInode->pIndir->aulEntries[ uEntry ] != BLOCK_SPARSE;
}
}
if( fBranch )
{
ret = BranchBlock( pInode, BRANCHDEPTH_DINDIR, false );
}
if( ret == 0 )
{
uint32_t ulBlock = pInode->ulLogicalBlock;
uint16_t uStart = pInode->uDindirEntry; /* pInode->uDindirEntry will change. */
for( uEntry = uStart; uEntry < INDIR_ENTRIES; uEntry++ )
{
/* Seek so that TruncIndir() has the correct indirect
* buffer and indirect entry.
*/
ret = RedInodeDataSeek( pInode, ulBlock );
if( ret == -RED_ENODATA )
{
ret = 0;
}
if( ( ret == 0 ) && ( pInode->ulIndirBlock != BLOCK_SPARSE ) )
{
bool fIndirFreed;
ret = TruncIndir( pInode, &fIndirFreed );
if( ret == 0 )
{
/* All of the indirects after the one which straddles
* the truncation boundary should definitely end up
* deleted.
*/
REDASSERT( ( uEntry == uStart ) || fIndirFreed );
/* If the double indirect is being freed, all of the
* indirects should be freed too.
*/
REDASSERT( fIndirFreed || fBranch );
if( fBranch && fIndirFreed )
{
pInode->pDindir->aulEntries[ uEntry ] = BLOCK_SPARSE;
}
}
}
if( ret != 0 )
{
break;
}
ulBlock += ( INDIR_ENTRIES - pInode->uIndirEntry );
}
if( ret == 0 )
{
*pfFreed = !fBranch;
if( !fBranch )
{
RedInodePutDindir( pInode );
ret = RedImapBlockSet( pInode->ulDindirBlock, false );
}
}
}
}
return ret;
}
#endif /* DINDIR_POINTERS > 0U */
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
/** @brief Truncate a indirect.
*
* @param pInode A pointer to the cached inode, whose coordinates indicate
* the truncation boundary.
* @param pfFreed On successful return, populated with whether the indirect
* node was freed.
*
* @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_ENOSPC Insufficient free space to perform the truncate.
* @retval -RED_EINVAL Invalid parameters.
*/
static REDSTATUS TruncIndir( CINODE * pInode,
bool * pfFreed )
{
REDSTATUS ret = 0;
if( !CINODE_IS_MOUNTED( pInode ) || ( pfFreed == NULL ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else if( pInode->pIndir == NULL )
{
*pfFreed = false;
}
else
{
bool fBranch = false;
uint16_t uEntry;
/* Scan the range of entries which are not being truncated. If there
* is anything there, then the indirect will not be empty after the
* truncate, so it is branched and modified instead of deleted.
*/
for( uEntry = 0U; !fBranch && ( uEntry < pInode->uIndirEntry ); uEntry++ )
{
fBranch = pInode->pIndir->aulEntries[ uEntry ] != BLOCK_SPARSE;
}
if( fBranch )
{
ret = BranchBlock( pInode, BRANCHDEPTH_INDIR, false );
}
if( ret == 0 )
{
for( uEntry = pInode->uIndirEntry; uEntry < INDIR_ENTRIES; uEntry++ )
{
ret = TruncDataBlock( pInode, &pInode->pIndir->aulEntries[ uEntry ], fBranch );
if( ret != 0 )
{
break;
}
}
if( ret == 0 )
{
*pfFreed = !fBranch;
if( !fBranch )
{
RedInodePutIndir( pInode );
ret = RedImapBlockSet( pInode->ulIndirBlock, false );
}
}
}
}
return ret;
}
#endif /* REDCONF_DIRECT_POINTERS < INODE_ENTRIES */
/** @brief Truncate a file data block.
*
* @param pInode A pointer to the cached inode structure.
* @param pulBlock On entry, contains the block to be truncated. On
* successful return, if @p fPropagate is true, populated
* with BLOCK_SPARSE, otherwise unmodified.
* @param fPropagate Whether the parent node is being branched.
*
* @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 TruncDataBlock( const CINODE * pInode,
uint32_t * pulBlock,
bool fPropagate )
{
REDSTATUS ret = 0;
if( !CINODE_IS_MOUNTED( pInode ) || ( pulBlock == NULL ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else if( *pulBlock != BLOCK_SPARSE )
{
ret = RedImapBlockSet( *pulBlock, false );
#if REDCONF_INODE_BLOCKS == 1
if( ret == 0 )
{
if( pInode->pInodeBuf->ulBlocks == 0U )
{
CRITICAL_ERROR();
ret = -RED_EFUBAR;
}
else
{
pInode->pInodeBuf->ulBlocks--;
}
}
#endif /* if REDCONF_INODE_BLOCKS == 1 */
if( ( ret == 0 ) && fPropagate )
{
*pulBlock = BLOCK_SPARSE;
}
}
else
{
/* Data block is sparse, nothing to truncate.
*/
}
return ret;
}
#endif /* DELETE_SUPPORTED || TRUNCATE_SUPPORTED */
/** @brief Prepare to increase the file size.
*
* When the inode size is increased, a sparse region is created. It is
* possible that a prior shrink operation to an unaligned size left stale data
* beyond the end of the file in the last data block. That data is not zeroed
* while shrinking the inode in order to transfer the disk full burden from the
* shrink operation to the expand operation.
*
* @param pInode A pointer to the cached inode structure.
*
* @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_ENOSPC Insufficient free space to perform the truncate.
* @retval -RED_EINVAL Invalid parameters.
*/
static REDSTATUS ExpandPrepare( CINODE * pInode )
{
REDSTATUS ret = 0;
if( !CINODE_IS_DIRTY( pInode ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
uint32_t ulOldSizeByteInBlock = ( uint32_t ) ( pInode->pInodeBuf->ullSize & ( REDCONF_BLOCK_SIZE - 1U ) );
if( ulOldSizeByteInBlock != 0U )
{
ret = RedInodeDataSeek( pInode, ( uint32_t ) ( pInode->pInodeBuf->ullSize >> BLOCK_SIZE_P2 ) );
if( ret == -RED_ENODATA )
{
ret = 0;
}
else if( ret == 0 )
{
ret = BranchBlock( pInode, BRANCHDEPTH_FILE_DATA, true );
if( ret == 0 )
{
RedMemSet( &pInode->pbData[ ulOldSizeByteInBlock ], 0U, REDCONF_BLOCK_SIZE - ulOldSizeByteInBlock );
}
}
else
{
REDERROR();
}
}
}
return ret;
}
#endif /* REDCONF_READ_ONLY == 0 */
/** @brief Seek to a given position within an inode, then buffer the data block.
*
* On successful return, pInode->pbData will be populated with a buffer
* corresponding to the @p ulBlock block offset.
*
* @param pInode A pointer to the cached inode structure.
* @param ulBlock The block offset to seek to and buffer.
*
* @return A negated ::REDSTATUS code indicating the operation result.
*
* @retval 0 Operation was successful.
* @retval -RED_ENODATA The block offset is sparse.
* @retval -RED_EINVAL @p ulBlock is too large.
* @retval -RED_EIO A disk I/O error occurred.
*/
REDSTATUS RedInodeDataSeekAndRead( CINODE * pInode,
uint32_t ulBlock )
{
REDSTATUS ret;
ret = RedInodeDataSeek( pInode, ulBlock );
if( ( ret == 0 ) && ( pInode->pbData == NULL ) )
{
REDASSERT( pInode->ulDataBlock != BLOCK_SPARSE );
ret = RedBufferGet( pInode->ulDataBlock, 0U, CAST_VOID_PTR_PTR( &pInode->pbData ) );
}
return ret;
}
/** @brief Seek to a given position within an inode.
*
* On successful return, pInode->ulDataBlock will be populated with the
* physical block number corresponding to the @p ulBlock block offset.
*
* Note: Callers of this function depend on its parameter checking.
*
* @param pInode A pointer to the cached inode structure.
* @param ulBlock The block offset to seek to.
*
* @return A negated ::REDSTATUS code indicating the operation result.
*
* @retval 0 Operation was successful.
* @retval -RED_ENODATA The block offset is sparse.
* @retval -RED_EINVAL @p ulBlock is too large; or @p pInode is not a
* mounted cached inode pointer.
* @retval -RED_EIO A disk I/O error occurred.
*/
REDSTATUS RedInodeDataSeek( CINODE * pInode,
uint32_t ulBlock )
{
REDSTATUS ret = 0;
if( !CINODE_IS_MOUNTED( pInode ) || ( ulBlock >= INODE_DATA_BLOCKS ) )
{
ret = -RED_EINVAL;
}
else
{
SeekCoord( pInode, ulBlock );
#if DINDIR_POINTERS > 0U
if( pInode->uDindirEntry != COORD_ENTRY_INVALID )
{
if( pInode->ulDindirBlock == BLOCK_SPARSE )
{
/* If the double indirect is unallocated, so is the indirect.
*/
pInode->ulIndirBlock = BLOCK_SPARSE;
}
else
{
if( pInode->pDindir == NULL )
{
ret = RedBufferGet( pInode->ulDindirBlock, BFLAG_META_DINDIR, CAST_VOID_PTR_PTR( &pInode->pDindir ) );
}
if( ret == 0 )
{
pInode->ulIndirBlock = pInode->pDindir->aulEntries[ pInode->uDindirEntry ];
}
}
}
#endif /* if DINDIR_POINTERS > 0U */
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
if( ( ret == 0 ) && ( pInode->uIndirEntry != COORD_ENTRY_INVALID ) )
{
if( pInode->ulIndirBlock == BLOCK_SPARSE )
{
/* If the indirect is unallocated, so is the data block.
*/
pInode->ulDataBlock = BLOCK_SPARSE;
}
else
{
if( pInode->pIndir == NULL )
{
ret = RedBufferGet( pInode->ulIndirBlock, BFLAG_META_INDIR, CAST_VOID_PTR_PTR( &pInode->pIndir ) );
}
if( ret == 0 )
{
pInode->ulDataBlock = pInode->pIndir->aulEntries[ pInode->uIndirEntry ];
}
}
}
#endif /* if REDCONF_DIRECT_POINTERS < INODE_ENTRIES */
if( ( ret == 0 ) && ( pInode->ulDataBlock == BLOCK_SPARSE ) )
{
ret = -RED_ENODATA;
}
}
return ret;
}
/** @brief Seek to the coordinates.
*
* Compute the new coordinates, and put any buffers which are not needed or are
* no longer appropriate.
*
* @param pInode A pointer to the cached inode structure.
* @param ulBlock The block offset to seek to.
*/
static void SeekCoord( CINODE * pInode,
uint32_t ulBlock )
{
if( !CINODE_IS_MOUNTED( pInode ) || ( ulBlock >= INODE_DATA_BLOCKS ) )
{
REDERROR();
}
else if( ( pInode->ulLogicalBlock != ulBlock ) || !pInode->fCoordInited )
{
RedInodePutData( pInode );
pInode->ulLogicalBlock = ulBlock;
#if REDCONF_DIRECT_POINTERS > 0U
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
if( ulBlock < REDCONF_DIRECT_POINTERS )
#endif
{
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
RedInodePutCoord( pInode );
#endif
pInode->uInodeEntry = ( uint16_t ) ulBlock;
pInode->ulDataBlock = pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ];
#if DINDIR_POINTERS > 0U
pInode->uDindirEntry = COORD_ENTRY_INVALID;
#endif
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
pInode->uIndirEntry = COORD_ENTRY_INVALID;
#endif
}
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
else
#endif
#endif /* if REDCONF_DIRECT_POINTERS > 0U */
#if REDCONF_INDIRECT_POINTERS > 0U
#if REDCONF_INDIRECT_POINTERS < INODE_ENTRIES
if( ulBlock < ( INODE_INDIR_BLOCKS + REDCONF_DIRECT_POINTERS ) )
#endif
{
uint32_t ulIndirRangeOffset = ulBlock - REDCONF_DIRECT_POINTERS;
uint16_t uInodeEntry = ( uint16_t ) ( ( ulIndirRangeOffset / INDIR_ENTRIES ) + REDCONF_DIRECT_POINTERS );
uint16_t uIndirEntry = ( uint16_t ) ( ulIndirRangeOffset % INDIR_ENTRIES );
#if DINDIR_POINTERS > 0U
RedInodePutDindir( pInode );
#endif
/* If the inode entry is not changing, then the previous indirect
* is still the correct one. Otherwise, the old indirect will be
* released and the new one will be read later.
*/
if( ( pInode->uInodeEntry != uInodeEntry ) || !pInode->fCoordInited )
{
RedInodePutIndir( pInode );
pInode->uInodeEntry = uInodeEntry;
pInode->ulIndirBlock = pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ];
}
#if DINDIR_POINTERS > 0U
pInode->uDindirEntry = COORD_ENTRY_INVALID;
#endif
pInode->uIndirEntry = uIndirEntry;
/* At this point, the following pInode members are needed but not
* yet populated:
*
* - pIndir
* - ulDataBlock
*/
}
#if DINDIR_POINTERS > 0U
else
#endif
#endif /* if REDCONF_INDIRECT_POINTERS > 0U */
#if DINDIR_POINTERS > 0U
{
uint32_t ulDindirRangeOffset = ( ulBlock - REDCONF_DIRECT_POINTERS ) - INODE_INDIR_BLOCKS;
uint16_t uInodeEntry = ( uint16_t ) ( ( ulDindirRangeOffset / DINDIR_DATA_BLOCKS ) + REDCONF_DIRECT_POINTERS + REDCONF_INDIRECT_POINTERS );
uint32_t ulDindirNodeOffset = ulDindirRangeOffset % DINDIR_DATA_BLOCKS;
uint16_t uDindirEntry = ( uint16_t ) ( ulDindirNodeOffset / INDIR_ENTRIES );
uint16_t uIndirEntry = ( uint16_t ) ( ulDindirNodeOffset % INDIR_ENTRIES );
/* If the inode entry is not changing, then the previous double
* indirect is still the correct one. Otherwise, the old double
* indirect will be released and the new one will be read later.
*/
if( ( pInode->uInodeEntry != uInodeEntry ) || !pInode->fCoordInited )
{
RedInodePutIndir( pInode );
RedInodePutDindir( pInode );
pInode->uInodeEntry = uInodeEntry;
pInode->ulDindirBlock = pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ];
}
/* If neither the inode entry nor double indirect entry are
* changing, then the previous indirect is still the correct one.
* Otherwise, it old indirect will be released and the new one will
* be read later.
*/
else if( pInode->uDindirEntry != uDindirEntry )
{
RedInodePutIndir( pInode );
}
else
{
/* Data buffer has already been put, nothing to do.
*/
}
pInode->uDindirEntry = uDindirEntry;
pInode->uIndirEntry = uIndirEntry;
/* At this point, the following pInode members are needed but not
* yet populated:
*
* - pDindir
* - pIndir
* - ulIndirBlock
* - ulDataBlock
*/
}
#elif ( REDCONF_DIRECT_POINTERS > 0U ) && ( REDCONF_INDIRECT_POINTERS > 0U )
else
{
/* There are no double indirects, so the block should have been in
* the direct or indirect range.
*/
REDERROR();
}
#endif /* if DINDIR_POINTERS > 0U */
pInode->fCoordInited = true;
}
else
{
/* Seeking to the current position, nothing to do.
*/
}
}
/** @brief Read an unaligned portion of a block.
*
* @param pInode A pointer to the cached inode structure.
* @param ullStart The file offset at which to read.
* @param ulLen The number of bytes to read.
* @param pbBuffer The buffer to read into.
*
* @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 ReadUnaligned( CINODE * pInode,
uint64_t ullStart,
uint32_t ulLen,
uint8_t * pbBuffer )
{
REDSTATUS ret;
/* This read should not cross a block boundary.
*/
if( ( ( ullStart >> BLOCK_SIZE_P2 ) != ( ( ( ullStart + ulLen ) - 1U ) >> BLOCK_SIZE_P2 ) ) ||
( pbBuffer == NULL ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
ret = RedInodeDataSeekAndRead( pInode, ( uint32_t ) ( ullStart >> BLOCK_SIZE_P2 ) );
if( ret == 0 )
{
RedMemCpy( pbBuffer, &pInode->pbData[ ullStart & ( REDCONF_BLOCK_SIZE - 1U ) ], ulLen );
}
else if( ret == -RED_ENODATA )
{
/* Sparse block, return zeroed data.
*/
RedMemSet( pbBuffer, 0U, ulLen );
ret = 0;
}
else
{
/* No action, just return the error.
*/
}
}
return ret;
}
/** @brief Read one or more whole blocks.
*
* @param pInode A pointer to the cached inode structure.
* @param ulBlockStart The file block offset at which to read.
* @param ulBlockCount The number of blocks to read.
* @param pbBuffer The buffer to read into.
*
* @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 ReadAligned( CINODE * pInode,
uint32_t ulBlockStart,
uint32_t ulBlockCount,
uint8_t * pbBuffer )
{
REDSTATUS ret = 0;
if( pbBuffer == NULL )
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
uint32_t ulBlockIndex = 0U;
/* Read the data from disk one contiguous extent at a time.
*/
while( ( ret == 0 ) && ( ulBlockIndex < ulBlockCount ) )
{
uint32_t ulExtentStart;
uint32_t ulExtentLen = ulBlockCount - ulBlockIndex;
ret = GetExtent( pInode, ulBlockStart + ulBlockIndex, &ulExtentStart, &ulExtentLen );
if( ret == 0 )
{
#if REDCONF_READ_ONLY == 0
/* Before reading directly from disk, flush any dirty file data
* buffers in the range to avoid reading stale data.
*/
ret = RedBufferFlush( ulExtentStart, ulExtentLen );
if( ret == 0 )
#endif
{
ret = RedIoRead( gbRedVolNum, ulExtentStart, ulExtentLen, &pbBuffer[ ulBlockIndex << BLOCK_SIZE_P2 ] );
if( ret == 0 )
{
ulBlockIndex += ulExtentLen;
}
}
}
else if( ret == -RED_ENODATA )
{
/* Sparse block, return zeroed data.
*/
RedMemSet( &pbBuffer[ ulBlockIndex << BLOCK_SIZE_P2 ], 0U, REDCONF_BLOCK_SIZE );
ulBlockIndex++;
ret = 0;
}
else
{
/* An unexpected error occurred; the loop will terminate.
*/
}
}
}
return ret;
}
#if REDCONF_READ_ONLY == 0
/** @brief Write an unaligned portion of a block.
*
* @param pInode A pointer to the cached inode structure.
* @param ullStart The file offset at which to write.
* @param ulLen The number of bytes to write.
* @param pbBuffer The buffer to write from.
*
* @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_ENOSPC No data can be written because there is insufficient
* free space.
* @retval -RED_EINVAL Invalid parameters.
*/
static REDSTATUS WriteUnaligned( CINODE * pInode,
uint64_t ullStart,
uint32_t ulLen,
const uint8_t * pbBuffer )
{
REDSTATUS ret;
/* This write should not cross a block boundary.
*/
if( ( ( ullStart >> BLOCK_SIZE_P2 ) != ( ( ( ullStart + ulLen ) - 1U ) >> BLOCK_SIZE_P2 ) ) ||
( pbBuffer == NULL ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
ret = RedInodeDataSeek( pInode, ( uint32_t ) ( ullStart >> BLOCK_SIZE_P2 ) );
if( ( ret == 0 ) || ( ret == -RED_ENODATA ) )
{
ret = BranchBlock( pInode, BRANCHDEPTH_FILE_DATA, true );
if( ret == 0 )
{
RedMemCpy( &pInode->pbData[ ullStart & ( REDCONF_BLOCK_SIZE - 1U ) ], pbBuffer, ulLen );
}
}
}
return ret;
}
/** @brief Write one or more whole blocks.
*
* @param pInode A pointer to the cached inode structure.
* @param ulBlockStart The file block offset at which to write.
* @param pulBlockCount On entry, the number of blocks to attempt to write.
* On successful return, the number of blocks actually
* written.
* @param pbBuffer The buffer to write from.
*
* @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_ENOSPC No data can be written because there is insufficient
* free space.
* @retval -RED_EINVAL Invalid parameters.
*/
static REDSTATUS WriteAligned( CINODE * pInode,
uint32_t ulBlockStart,
uint32_t * pulBlockCount,
const uint8_t * pbBuffer )
{
REDSTATUS ret = 0;
if( ( pulBlockCount == NULL ) || ( pbBuffer == NULL ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
bool fFull = false;
uint32_t ulBlockCount = *pulBlockCount;
uint32_t ulBlockIndex;
/* Branch all of the file data blocks in advance.
*/
for( ulBlockIndex = 0U; ( ulBlockIndex < ulBlockCount ) && !fFull; ulBlockIndex++ )
{
ret = RedInodeDataSeek( pInode, ulBlockStart + ulBlockIndex );
if( ( ret == 0 ) || ( ret == -RED_ENODATA ) )
{
ret = BranchBlock( pInode, BRANCHDEPTH_FILE_DATA, false );
if( ret == -RED_ENOSPC )
{
if( ulBlockIndex > 0U )
{
ret = 0;
}
fFull = true;
}
}
if( ret != 0 )
{
break;
}
}
ulBlockCount = ulBlockIndex;
ulBlockIndex = 0U;
if( fFull )
{
ulBlockCount--;
}
/* Write the data to disk one contiguous extent at a time.
*/
while( ( ret == 0 ) && ( ulBlockIndex < ulBlockCount ) )
{
uint32_t ulExtentStart;
uint32_t ulExtentLen = ulBlockCount - ulBlockIndex;
ret = GetExtent( pInode, ulBlockStart + ulBlockIndex, &ulExtentStart, &ulExtentLen );
if( ret == 0 )
{
ret = RedIoWrite( gbRedVolNum, ulExtentStart, ulExtentLen, &pbBuffer[ ulBlockIndex << BLOCK_SIZE_P2 ] );
if( ret == 0 )
{
/* If there is any buffered file data for the extent we
* just wrote, those buffers are now stale.
*/
ret = RedBufferDiscardRange( ulExtentStart, ulExtentLen );
}
if( ret == 0 )
{
ulBlockIndex += ulExtentLen;
}
}
}
if( ret == 0 )
{
*pulBlockCount = ulBlockCount;
}
}
return ret;
}
#endif /* REDCONF_READ_ONLY == 0 */
/** @brief Get the physical block number and count of contiguous blocks given a
* starting logical block number.
*
* @param pInode A pointer to the cached inode structure.
* @param ulBlockStart The file block offset for the start of the extent.
* @param pulExtentStart On successful return, the starting physical block
* number of the contiguous extent.
* @param pulExtentLen On entry, the maximum length of the extent; on
* successful return, the length of the contiguous
* extent.
*
* @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_ENODATA The block offset is sparse.
* @retval -RED_EINVAL Invalid parameters.
*/
static REDSTATUS GetExtent( CINODE * pInode,
uint32_t ulBlockStart,
uint32_t * pulExtentStart,
uint32_t * pulExtentLen )
{
REDSTATUS ret;
if( ( pulExtentStart == NULL ) || ( pulExtentLen == NULL ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
ret = RedInodeDataSeek( pInode, ulBlockStart );
if( ret == 0 )
{
uint32_t ulExtentLen = *pulExtentLen;
uint32_t ulFirstBlock = pInode->ulDataBlock;
uint32_t ulRunLen = 1U;
while( ( ret == 0 ) && ( ulRunLen < ulExtentLen ) )
{
ret = RedInodeDataSeek( pInode, ulBlockStart + ulRunLen );
/* The extent ends when we find a sparse data block or when the
* data block is not contiguous with the preceding data block.
*/
if( ( ret == -RED_ENODATA ) || ( ( ret == 0 ) && ( pInode->ulDataBlock != ( ulFirstBlock + ulRunLen ) ) ) )
{
ret = 0;
break;
}
ulRunLen++;
}
if( ret == 0 )
{
*pulExtentStart = ulFirstBlock;
*pulExtentLen = ulRunLen;
}
}
}
return ret;
}
#if REDCONF_READ_ONLY == 0
/** @brief Allocate or branch the file metadata path and data block if necessary.
*
* Optionally, can stop allocating/branching at a certain depth.
*
* @param pInode A pointer to the cached inode structure.
* @param depth A BRANCHDEPTH_ value indicating the lowest depth to branch.
* @param fBuffer Whether to buffer the data block.
*
* @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_ENOSPC No data can be written because there is insufficient
* free space.
*/
static REDSTATUS BranchBlock( CINODE * pInode,
BRANCHDEPTH depth,
bool fBuffer )
{
REDSTATUS ret;
uint32_t ulCost = 0U; /* Init'd to quiet warnings. */
ret = BranchBlockCost( pInode, depth, &ulCost );
if( ( ret == 0 ) && ( ulCost > FreeBlockCount() ) )
{
ret = -RED_ENOSPC;
}
if( ret == 0 )
{
#if DINDIR_POINTERS > 0U
if( pInode->uDindirEntry != COORD_ENTRY_INVALID )
{
ret = BranchOneBlock( &pInode->ulDindirBlock, CAST_VOID_PTR_PTR( &pInode->pDindir ), BFLAG_META_DINDIR );
if( ret == 0 )
{
/* In case we just created the double indirect.
*/
pInode->pDindir->ulInode = pInode->ulInode;
pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ] = pInode->ulDindirBlock;
}
}
if( ret == 0 )
#endif /* if DINDIR_POINTERS > 0U */
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
{
if( ( pInode->uIndirEntry != COORD_ENTRY_INVALID ) && ( depth >= BRANCHDEPTH_INDIR ) )
{
ret = BranchOneBlock( &pInode->ulIndirBlock, CAST_VOID_PTR_PTR( &pInode->pIndir ), BFLAG_META_INDIR );
if( ret == 0 )
{
/* In case we just created the indirect.
*/
pInode->pIndir->ulInode = pInode->ulInode;
#if DINDIR_POINTERS > 0U
if( pInode->uDindirEntry != COORD_ENTRY_INVALID )
{
pInode->pDindir->aulEntries[ pInode->uDindirEntry ] = pInode->ulIndirBlock;
}
else
#endif
{
pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ] = pInode->ulIndirBlock;
}
}
}
}
if( ret == 0 )
#endif /* if REDCONF_DIRECT_POINTERS < INODE_ENTRIES */
{
if( depth == BRANCHDEPTH_FILE_DATA )
{
#if REDCONF_INODE_BLOCKS == 1
bool fAllocedNew = ( pInode->ulDataBlock == BLOCK_SPARSE );
#endif
void ** ppBufPtr = ( fBuffer || ( pInode->pbData != NULL ) ) ? CAST_VOID_PTR_PTR( &pInode->pbData ) : NULL;
ret = BranchOneBlock( &pInode->ulDataBlock, ppBufPtr, 0U );
if( ret == 0 )
{
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
if( pInode->uIndirEntry != COORD_ENTRY_INVALID )
{
pInode->pIndir->aulEntries[ pInode->uIndirEntry ] = pInode->ulDataBlock;
}
else
#endif
{
pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ] = pInode->ulDataBlock;
}
#if REDCONF_INODE_BLOCKS == 1
if( fAllocedNew )
{
if( pInode->pInodeBuf->ulBlocks < INODE_DATA_BLOCKS )
{
pInode->pInodeBuf->ulBlocks++;
}
else
{
CRITICAL_ERROR();
ret = -RED_EFUBAR;
}
}
#endif /* if REDCONF_INODE_BLOCKS == 1 */
}
}
}
CRITICAL_ASSERT( ret == 0 );
}
return ret;
}
/** @brief Branch a block.
*
* The block can be a double indirect, indirect, or file data block.
*
* The caller should have already handled the disk full implications of
* branching this block.
*
* @param pulBlock On entry, the current block number, which may be
* BLOCK_SPARSE if the block is to be newly allocated. On
* successful return, populated with the new block number,
* which may be the same as the original block number if it
* was not BLOCK_SPARSE and the block was already branched.
* @param ppBuffer If NULL, indicates that the caller does not want to buffer
* the branched block. If non-NULL, the caller does want the
* branched block buffered, and the following is true: On
* entry, the current buffer for the block, if there is one, or
* NULL if there is no buffer. On successful exit, populated
* with a buffer for the block, which will be dirty. If the
* block number is initially BLOCK_SPARSE, there should be no
* buffer for the block.
* @param uBFlag The buffer type flags: BFLAG_META_DINDIR, BFLAG_META_INDIR,
* or zero for file data.
*
* @retval 0 Operation was successful.
* @retval -RED_EIO A disk I/O error occurred.
* @retval -RED_EINVAL Invalid parameters.
*/
static REDSTATUS BranchOneBlock( uint32_t * pulBlock,
void ** ppBuffer,
uint16_t uBFlag )
{
REDSTATUS ret = 0;
if( pulBlock == NULL )
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
ALLOCSTATE state = ALLOCSTATE_FREE;
uint32_t ulPrevBlock = *pulBlock;
if( ulPrevBlock != BLOCK_SPARSE )
{
ret = RedImapBlockState( ulPrevBlock, &state );
}
if( ret == 0 )
{
if( state == ALLOCSTATE_NEW )
{
/* Block is already branched, so simply get it buffered dirty
* if requested.
*/
if( ppBuffer != NULL )
{
if( *ppBuffer != NULL )
{
RedBufferDirty( *ppBuffer );
}
else
{
ret = RedBufferGet( ulPrevBlock, uBFlag | BFLAG_DIRTY, ppBuffer );
}
}
}
else
{
/* Block does not exist or is committed state, so allocate a
* new block for the branch.
*/
ret = RedImapAllocBlock( pulBlock );
if( ret == 0 )
{
if( ulPrevBlock == BLOCK_SPARSE )
{
/* Block did not exist previously, so just get it
* buffered if requested.
*/
if( ppBuffer != NULL )
{
if( *ppBuffer != NULL )
{
/* How could there be an existing buffer when
* the block did not exist?
*/
REDERROR();
ret = -RED_EINVAL;
}
else
{
ret = RedBufferGet( *pulBlock, ( uint16_t ) ( ( uint32_t ) uBFlag | BFLAG_NEW | BFLAG_DIRTY ), ppBuffer );
}
}
}
else
{
/* Branch the buffer for the committed state block to
* the newly allocated location.
*/
if( ppBuffer != NULL )
{
if( *ppBuffer == NULL )
{
ret = RedBufferGet( ulPrevBlock, uBFlag, ppBuffer );
}
if( ret == 0 )
{
RedBufferBranch( *ppBuffer, *pulBlock );
}
}
/* Mark the committed state block almost free.
*/
if( ret == 0 )
{
ret = RedImapBlockSet( ulPrevBlock, false );
}
}
}
}
}
}
return ret;
}
/** @brief Compute the free space cost of branching a block.
*
* The caller must first use RedInodeDataSeek() to the block to be branched.
*
* @param pInode A pointer to the cached inode structure, whose coordinates
* indicate the block to be branched.
* @param depth A BRANCHDEPTH_ value indicating how much of the file
* metadata structure needs to be branched.
* @param pulCost On successful return, populated with the number of blocks
* that must be allocated from free space in order to branch
* the given block.
*
* @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 BranchBlockCost( const CINODE * pInode,
BRANCHDEPTH depth,
uint32_t * pulCost )
{
REDSTATUS ret = 0;
if( !CINODE_IS_MOUNTED( pInode ) || !pInode->fCoordInited || ( depth > BRANCHDEPTH_MAX ) || ( pulCost == NULL ) )
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
ALLOCSTATE state;
/* ulCost is initialized to the maximum number of blocks that could
* be branched, and decremented for every block we determine does not
* need to be branched.
*/
#if DINDIR_POINTERS > 0U
uint32_t ulCost = 3U;
#elif REDCONF_DIRECT_POINTERS < INODE_ENTRIES
uint32_t ulCost = 2U;
#else
uint32_t ulCost = 1U;
#endif
#if DINDIR_POINTERS > 0U
if( pInode->uDindirEntry != COORD_ENTRY_INVALID )
{
if( pInode->ulDindirBlock != BLOCK_SPARSE )
{
ret = RedImapBlockState( pInode->ulDindirBlock, &state );
if( ( ret == 0 ) && ( state == ALLOCSTATE_NEW ) )
{
/* Double indirect already branched.
*/
ulCost--;
}
}
}
else
{
/* At this inode offset there are no double indirects.
*/
ulCost--;
}
if( ret == 0 )
#endif /* if DINDIR_POINTERS > 0U */
#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
{
if( ( pInode->uIndirEntry != COORD_ENTRY_INVALID ) && ( depth >= BRANCHDEPTH_INDIR ) )
{
if( pInode->ulIndirBlock != BLOCK_SPARSE )
{
ret = RedImapBlockState( pInode->ulIndirBlock, &state );
if( ( ret == 0 ) && ( state == ALLOCSTATE_NEW ) )
{
/* Indirect already branched.
*/
ulCost--;
}
}
}
else
{
/* Either not branching this deep, or at this inode offset
* there are no indirects.
*/
ulCost--;
}
}
if( ret == 0 )
#endif /* if REDCONF_DIRECT_POINTERS < INODE_ENTRIES */
{
if( depth == BRANCHDEPTH_FILE_DATA )
{
if( pInode->ulDataBlock != BLOCK_SPARSE )
{
ret = RedImapBlockState( pInode->ulDataBlock, &state );
if( ( ret == 0 ) && ( state == ALLOCSTATE_NEW ) )
{
/* File data block already branched.
*/
ulCost--;
/* If the file data block is branched, then its parent
* nodes should be branched as well.
*/
REDASSERT( ulCost == 0U );
}
}
}
else
{
/* Not branching this deep.
*/
ulCost--;
}
}
if( ret == 0 )
{
*pulCost = ulCost;
}
}
return ret;
}
/** @brief Yields the number of currently available free blocks.
*
* Accounts for reserved blocks, subtracting the number of reserved blocks if
* they are unavailable.
*
* @return Number of currently available free blocks.
*/
static uint32_t FreeBlockCount( void )
{
uint32_t ulFreeBlocks = gpRedMR->ulFreeBlocks;
#if RESERVED_BLOCKS > 0U
if( !gpRedCoreVol->fUseReservedBlocks )
{
if( ulFreeBlocks >= RESERVED_BLOCKS )
{
ulFreeBlocks -= RESERVED_BLOCKS;
}
else
{
ulFreeBlocks = 0U;
}
}
#endif /* if RESERVED_BLOCKS > 0U */
return ulFreeBlocks;
}
#endif /* REDCONF_READ_ONLY == 0 */