/* ----> 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 #include /* 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 */