mirror of
https://github.com/FreeRTOS/FreeRTOS-Kernel.git
synced 2025-04-20 21:41:59 -04:00
1918 lines
60 KiB
C
1918 lines
60 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
|
|
static REDSTATUS ExpandPrepare(CINODE *pInode);
|
|
#endif
|
|
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
|
|
|
|
|
|
/** @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.
|
|
@praam 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_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 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
|
|
}
|
|
|
|
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((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 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((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_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 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
|
|
|
|
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 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(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
|
|
}
|
|
}
|
|
}
|
|
|
|
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 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(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
|
|
|
|
return ulFreeBlocks;
|
|
}
|
|
#endif /* REDCONF_READ_ONLY == 0 */
|
|
|