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