Initial version
This commit is contained in:
@ -0,0 +1,198 @@
|
||||
/* ----> 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 block device I/O using logical blocks as the units.
|
||||
|
||||
The OS block device implementations operate on sectors. The core does I/O
|
||||
in terms of logical blocks: this module translates from logical blocks to
|
||||
sectors.
|
||||
|
||||
If bBlockIoRetries is greater than 0 for the current volume, then this
|
||||
module will retry block device calls on failure up to the configured number
|
||||
of times. This behavior caters to the type of unreliable hardware and
|
||||
drivers that are sometimes found in the IoT world, where one operation may
|
||||
fail but the next may still succeed.
|
||||
*/
|
||||
#include <redfs.h>
|
||||
#include <redcore.h>
|
||||
|
||||
|
||||
/** @brief Read a range of logical blocks.
|
||||
|
||||
@param bVolNum The volume whose block device is being read from.
|
||||
@param ulBlockStart The first block to read.
|
||||
@param ulBlockCount The number of blocks to read.
|
||||
@param pBuffer The buffer to populate with the data read.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
@retval -RED_EINVAL Invalid parameters.
|
||||
*/
|
||||
REDSTATUS RedIoRead(
|
||||
uint8_t bVolNum,
|
||||
uint32_t ulBlockStart,
|
||||
uint32_t ulBlockCount,
|
||||
void *pBuffer)
|
||||
{
|
||||
REDSTATUS ret = 0;
|
||||
|
||||
if( (bVolNum >= REDCONF_VOLUME_COUNT)
|
||||
|| (ulBlockStart >= gaRedVolume[bVolNum].ulBlockCount)
|
||||
|| ((gaRedVolume[bVolNum].ulBlockCount - ulBlockStart) < ulBlockCount)
|
||||
|| (ulBlockCount == 0U)
|
||||
|| (pBuffer == NULL))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t bSectorShift = gaRedVolume[bVolNum].bBlockSectorShift;
|
||||
uint64_t ullSectorStart = (uint64_t)ulBlockStart << bSectorShift;
|
||||
uint32_t ulSectorCount = ulBlockCount << bSectorShift;
|
||||
uint8_t bRetryIdx;
|
||||
|
||||
REDASSERT(bSectorShift < 32U);
|
||||
REDASSERT((ulSectorCount >> bSectorShift) == ulBlockCount);
|
||||
|
||||
for(bRetryIdx = 0U; bRetryIdx <= gpRedVolConf->bBlockIoRetries; bRetryIdx++)
|
||||
{
|
||||
ret = RedOsBDevRead(bVolNum, ullSectorStart, ulSectorCount, pBuffer);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CRITICAL_ASSERT(ret == 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
/** @brief Write a range of logical blocks.
|
||||
|
||||
@param bVolNum The volume whose block device is being written to.
|
||||
@param ulBlockStart The first block to write.
|
||||
@param ulBlockCount The number of blocks to write.
|
||||
@param pBuffer The buffer containing the data to write.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
@retval -RED_EINVAL Invalid parameters.
|
||||
*/
|
||||
REDSTATUS RedIoWrite(
|
||||
uint8_t bVolNum,
|
||||
uint32_t ulBlockStart,
|
||||
uint32_t ulBlockCount,
|
||||
const void *pBuffer)
|
||||
{
|
||||
REDSTATUS ret = 0;
|
||||
|
||||
if( (bVolNum >= REDCONF_VOLUME_COUNT)
|
||||
|| (ulBlockStart >= gaRedVolume[bVolNum].ulBlockCount)
|
||||
|| ((gaRedVolume[bVolNum].ulBlockCount - ulBlockStart) < ulBlockCount)
|
||||
|| (ulBlockCount == 0U)
|
||||
|| (pBuffer == NULL))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t bSectorShift = gaRedVolume[bVolNum].bBlockSectorShift;
|
||||
uint64_t ullSectorStart = (uint64_t)ulBlockStart << bSectorShift;
|
||||
uint32_t ulSectorCount = ulBlockCount << bSectorShift;
|
||||
uint8_t bRetryIdx;
|
||||
|
||||
REDASSERT(bSectorShift < 32U);
|
||||
REDASSERT((ulSectorCount >> bSectorShift) == ulBlockCount);
|
||||
|
||||
for(bRetryIdx = 0U; bRetryIdx <= gpRedVolConf->bBlockIoRetries; bRetryIdx++)
|
||||
{
|
||||
ret = RedOsBDevWrite(bVolNum, ullSectorStart, ulSectorCount, pBuffer);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CRITICAL_ASSERT(ret == 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** @brief Flush any caches beneath the file system.
|
||||
|
||||
@param bVolNum The volume number of the volume whose block device is being
|
||||
flushed.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p bVolNum is an invalid volume number.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
*/
|
||||
REDSTATUS RedIoFlush(
|
||||
uint8_t bVolNum)
|
||||
{
|
||||
REDSTATUS ret = 0;
|
||||
|
||||
if(bVolNum >= REDCONF_VOLUME_COUNT)
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t bRetryIdx;
|
||||
|
||||
for(bRetryIdx = 0U; bRetryIdx <= gpRedVolConf->bBlockIoRetries; bRetryIdx++)
|
||||
{
|
||||
ret = RedOsBDevFlush(bVolNum);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CRITICAL_ASSERT(ret == 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* REDCONF_READ_ONLY == 0 */
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,958 @@
|
||||
/* ----> 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 directory operations.
|
||||
*/
|
||||
#include <redfs.h>
|
||||
|
||||
#if REDCONF_API_POSIX == 1
|
||||
|
||||
#include <redcore.h>
|
||||
|
||||
|
||||
#define DIR_INDEX_INVALID UINT32_MAX
|
||||
|
||||
#if (REDCONF_NAME_MAX % 4U) != 0U
|
||||
#define DIRENT_PADDING (4U - (REDCONF_NAME_MAX % 4U))
|
||||
#else
|
||||
#define DIRENT_PADDING (0U)
|
||||
#endif
|
||||
#define DIRENT_SIZE (4U + REDCONF_NAME_MAX + DIRENT_PADDING)
|
||||
#define DIRENTS_PER_BLOCK (REDCONF_BLOCK_SIZE / DIRENT_SIZE)
|
||||
#define DIRENTS_MAX (uint32_t)REDMIN(UINT32_MAX, UINT64_SUFFIX(1) * INODE_DATA_BLOCKS * DIRENTS_PER_BLOCK)
|
||||
|
||||
|
||||
/** @brief On-disk directory entry.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
/** The inode number that the directory entry points at. If the directory
|
||||
entry is available, this holds INODE_INVALID.
|
||||
*/
|
||||
uint32_t ulInode;
|
||||
|
||||
/** The name of the directory entry. For names shorter than
|
||||
REDCONF_NAME_MAX, unused bytes in the array are zeroed. For names of
|
||||
the maximum length, the string is not null terminated.
|
||||
*/
|
||||
char acName[REDCONF_NAME_MAX];
|
||||
|
||||
#if DIRENT_PADDING > 0U
|
||||
/** Unused padding so that ulInode is always aligned on a four-byte
|
||||
boundary.
|
||||
*/
|
||||
uint8_t abPadding[DIRENT_PADDING];
|
||||
#endif
|
||||
} DIRENT;
|
||||
|
||||
|
||||
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1)
|
||||
static REDSTATUS DirCyclicRenameCheck(uint32_t ulSrcInode, const CINODE *pDstPInode);
|
||||
#endif
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
static REDSTATUS DirEntryWrite(CINODE *pPInode, uint32_t ulIdx, uint32_t ulInode, const char *pszName, uint32_t ulNameLen);
|
||||
static uint64_t DirEntryIndexToOffset(uint32_t ulIdx);
|
||||
#endif
|
||||
static uint32_t DirOffsetToEntryIndex(uint64_t ullOffset);
|
||||
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
/** @brief Create a new entry in a directory.
|
||||
|
||||
@param pPInode A pointer to the cached inode structure of the directory
|
||||
to which the new entry will be added.
|
||||
@param pszName The name to be given to the new entry, terminated by a
|
||||
null or a path separator.
|
||||
@param ulInode The inode number the new name will point at.
|
||||
|
||||
@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 There is not enough space on the volume to
|
||||
create the new directory entry; or the directory
|
||||
is full.
|
||||
@retval -RED_ENOTDIR @p pPInode is not a directory.
|
||||
@retval -RED_ENAMETOOLONG @p pszName is too long.
|
||||
@retval -RED_EEXIST @p pszName already exists in @p ulPInode.
|
||||
@retval -RED_EINVAL @p pPInode is not a mounted dirty cached inode
|
||||
structure; or @p pszName is not a valid name.
|
||||
*/
|
||||
REDSTATUS RedDirEntryCreate(
|
||||
CINODE *pPInode,
|
||||
const char *pszName,
|
||||
uint32_t ulInode)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if(!CINODE_IS_DIRTY(pPInode) || (pszName == NULL) || !INODE_IS_VALID(ulInode))
|
||||
{
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(!pPInode->fDirectory)
|
||||
{
|
||||
ret = -RED_ENOTDIR;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulNameLen = RedNameLen(pszName);
|
||||
|
||||
if(ulNameLen == 0U)
|
||||
{
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(ulNameLen > REDCONF_NAME_MAX)
|
||||
{
|
||||
ret = -RED_ENAMETOOLONG;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulEntryIdx;
|
||||
|
||||
ret = RedDirEntryLookup(pPInode, pszName, &ulEntryIdx, NULL);
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = -RED_EEXIST;
|
||||
}
|
||||
else if(ret == -RED_ENOENT)
|
||||
{
|
||||
if(ulEntryIdx == DIR_INDEX_INVALID)
|
||||
{
|
||||
ret = -RED_ENOSPC;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Unexpected error, no action.
|
||||
*/
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = DirEntryWrite(pPInode, ulEntryIdx, ulInode, pszName, ulNameLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* REDCONF_READ_ONLY == 0 */
|
||||
|
||||
|
||||
#if DELETE_SUPPORTED
|
||||
/** @brief Delete an existing directory entry.
|
||||
|
||||
@param pPInode A pointer to the cached inode structure of the directory
|
||||
containing the entry to be deleted.
|
||||
@param ulDeleteIdx Position within the directory of the entry to be
|
||||
deleted.
|
||||
|
||||
@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 The file system does not have enough space to modify
|
||||
the parent directory to perform the deletion.
|
||||
@retval -RED_ENOTDIR @p pPInode is not a directory.
|
||||
@retval -RED_EINVAL @p pPInode is not a mounted dirty cached inode
|
||||
structure; or @p ulIdx is out of range.
|
||||
*/
|
||||
REDSTATUS RedDirEntryDelete(
|
||||
CINODE *pPInode,
|
||||
uint32_t ulDeleteIdx)
|
||||
{
|
||||
REDSTATUS ret = 0;
|
||||
|
||||
if(!CINODE_IS_DIRTY(pPInode) || (ulDeleteIdx >= DIRENTS_MAX))
|
||||
{
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(!pPInode->fDirectory)
|
||||
{
|
||||
ret = -RED_ENOTDIR;
|
||||
}
|
||||
else if((DirEntryIndexToOffset(ulDeleteIdx) + DIRENT_SIZE) == pPInode->pInodeBuf->ullSize)
|
||||
{
|
||||
/* Start searching one behind the index to be deleted.
|
||||
*/
|
||||
uint32_t ulTruncIdx = ulDeleteIdx - 1U;
|
||||
bool fDone = false;
|
||||
|
||||
/* We are deleting the last dirent in the directory, so search
|
||||
backwards to find the last populated dirent, allowing us to truncate
|
||||
the directory to that point.
|
||||
*/
|
||||
while((ret == 0) && (ulTruncIdx != UINT32_MAX) && !fDone)
|
||||
{
|
||||
ret = RedInodeDataSeekAndRead(pPInode, ulTruncIdx / DIRENTS_PER_BLOCK);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
const DIRENT *pDirents = CAST_CONST_DIRENT_PTR(pPInode->pbData);
|
||||
uint32_t ulBlockIdx = ulTruncIdx % DIRENTS_PER_BLOCK;
|
||||
|
||||
do
|
||||
{
|
||||
if(pDirents[ulBlockIdx].ulInode != INODE_INVALID)
|
||||
{
|
||||
fDone = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ulTruncIdx--;
|
||||
ulBlockIdx--;
|
||||
} while(ulBlockIdx != UINT32_MAX);
|
||||
}
|
||||
else if(ret == -RED_ENODATA)
|
||||
{
|
||||
ret = 0;
|
||||
|
||||
REDASSERT((ulTruncIdx % DIRENTS_PER_BLOCK) == 0U);
|
||||
ulTruncIdx -= DIRENTS_PER_BLOCK;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Unexpected error, loop will terminate; nothing else
|
||||
to be done.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
/* Currently ulTruncIdx represents the last valid dirent index, or
|
||||
UINT32_MAX if the directory is now empty. Increment it so that it
|
||||
represents the first invalid entry, which will be truncated.
|
||||
*/
|
||||
ulTruncIdx++;
|
||||
|
||||
/* Truncate the directory, deleting the requested entry and any empty
|
||||
dirents at the end of the directory.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedInodeDataTruncate(pPInode, DirEntryIndexToOffset(ulTruncIdx));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The dirent to delete is not the last entry in the directory, so just
|
||||
zero it.
|
||||
*/
|
||||
ret = DirEntryWrite(pPInode, ulDeleteIdx, INODE_INVALID, "", 0U);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* DELETE_SUPPORTED */
|
||||
|
||||
|
||||
/** @brief Perform a case-sensitive search of a directory for a given name.
|
||||
|
||||
If found, then position of the entry within the directory and the inode
|
||||
number it points to are returned. As an optimization for directory entry
|
||||
creation, in the case where the requested entry does not exist, the position
|
||||
of the first available (unused) entry is returned.
|
||||
|
||||
@param pPInode A pointer to the cached inode structure of the directory
|
||||
to search.
|
||||
@param pszName The name of the desired entry, terminated by either a
|
||||
null or a path separator.
|
||||
@param pulEntryIdx On successful return, meaning that the desired entry
|
||||
exists, populated with the position of the entry. If
|
||||
returning an -RED_ENOENT error, populated with the
|
||||
position of the first available entry, or set to
|
||||
DIR_INDEX_INVALID if the directory is full. Optional.
|
||||
@param pulInode On successful return, populated with the inode number
|
||||
that the name points to. Optional; may be `NULL`.
|
||||
|
||||
@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_ENOENT @p pszName does not name an existing file or
|
||||
directory.
|
||||
@retval -RED_ENOTDIR @p pPInode is not a directory.
|
||||
@retval -RED_EINVAL @p pPInode is not a mounted cached inode
|
||||
structure; or @p pszName is not a valid name; or
|
||||
@p pulEntryIdx is `NULL`.
|
||||
@retval -RED_ENAMETOOLONG @p pszName is too long.
|
||||
*/
|
||||
REDSTATUS RedDirEntryLookup(
|
||||
CINODE *pPInode,
|
||||
const char *pszName,
|
||||
uint32_t *pulEntryIdx,
|
||||
uint32_t *pulInode)
|
||||
{
|
||||
REDSTATUS ret = 0;
|
||||
|
||||
if(!CINODE_IS_MOUNTED(pPInode) || (pszName == NULL))
|
||||
{
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(!pPInode->fDirectory)
|
||||
{
|
||||
ret = -RED_ENOTDIR;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulNameLen = RedNameLen(pszName);
|
||||
|
||||
if(ulNameLen == 0U)
|
||||
{
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(ulNameLen > REDCONF_NAME_MAX)
|
||||
{
|
||||
ret = -RED_ENAMETOOLONG;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulIdx = 0U;
|
||||
uint32_t ulDirentCount = DirOffsetToEntryIndex(pPInode->pInodeBuf->ullSize);
|
||||
uint32_t ulFreeIdx = DIR_INDEX_INVALID; /* Index of first free dirent. */
|
||||
|
||||
/* Loop over the directory blocks, searching each block for a
|
||||
dirent that matches the given name.
|
||||
*/
|
||||
while((ret == 0) && (ulIdx < ulDirentCount))
|
||||
{
|
||||
ret = RedInodeDataSeekAndRead(pPInode, ulIdx / DIRENTS_PER_BLOCK);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
const DIRENT *pDirents = CAST_CONST_DIRENT_PTR(pPInode->pbData);
|
||||
uint32_t ulBlockLastIdx = REDMIN(DIRENTS_PER_BLOCK, ulDirentCount - ulIdx);
|
||||
uint32_t ulBlockIdx;
|
||||
|
||||
for(ulBlockIdx = 0U; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++)
|
||||
{
|
||||
const DIRENT *pDirent = &pDirents[ulBlockIdx];
|
||||
|
||||
if(pDirent->ulInode != INODE_INVALID)
|
||||
{
|
||||
/* The name in the dirent will not be null
|
||||
terminated if it is of the maximum length, so
|
||||
use a bounded string compare and then make sure
|
||||
there is nothing more to the name.
|
||||
*/
|
||||
if( (RedStrNCmp(pDirent->acName, pszName, ulNameLen) == 0)
|
||||
&& ((ulNameLen == REDCONF_NAME_MAX) || (pDirent->acName[ulNameLen] == '\0')))
|
||||
{
|
||||
/* Found a matching dirent, stop and return its
|
||||
information.
|
||||
*/
|
||||
if(pulInode != NULL)
|
||||
{
|
||||
*pulInode = pDirent->ulInode;
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
*pulInode = RedRev32(*pulInode);
|
||||
#endif
|
||||
}
|
||||
|
||||
ulIdx += ulBlockIdx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(ulFreeIdx == DIR_INDEX_INVALID)
|
||||
{
|
||||
ulFreeIdx = ulIdx + ulBlockIdx;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The directory entry is free, but we already found a free one, so there's
|
||||
nothing to do here.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
if(ulBlockIdx < ulBlockLastIdx)
|
||||
{
|
||||
/* If we broke out of the for loop, we found a matching
|
||||
dirent and can stop the search.
|
||||
*/
|
||||
break;
|
||||
}
|
||||
|
||||
ulIdx += ulBlockLastIdx;
|
||||
}
|
||||
else if(ret == -RED_ENODATA)
|
||||
{
|
||||
if(ulFreeIdx == DIR_INDEX_INVALID)
|
||||
{
|
||||
ulFreeIdx = ulIdx;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
ulIdx += DIRENTS_PER_BLOCK;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Unexpected error, let the loop terminate, no action
|
||||
here.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
/* If we made it all the way to the end of the directory
|
||||
without stopping, then the given name does not exist in the
|
||||
directory.
|
||||
*/
|
||||
if(ulIdx == ulDirentCount)
|
||||
{
|
||||
/* If the directory had no sparse dirents, then the first
|
||||
free dirent is beyond the end of the directory. If the
|
||||
directory is already the maximum size, then there is no
|
||||
free dirent.
|
||||
*/
|
||||
if((ulFreeIdx == DIR_INDEX_INVALID) && (ulDirentCount < DIRENTS_MAX))
|
||||
{
|
||||
ulFreeIdx = ulDirentCount;
|
||||
}
|
||||
|
||||
ulIdx = ulFreeIdx;
|
||||
|
||||
ret = -RED_ENOENT;
|
||||
}
|
||||
|
||||
if(pulEntryIdx != NULL)
|
||||
{
|
||||
*pulEntryIdx = ulIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#if (REDCONF_API_POSIX_READDIR == 1) || (REDCONF_CHECKER == 1)
|
||||
/** @brief Read the next entry from a directory, given a starting index.
|
||||
|
||||
@param pPInode A pointer to the cached inode structure of the directory to
|
||||
read from.
|
||||
@param pulIdx On entry, the directory index to start reading from. On
|
||||
successful return, populated with the directory index to use
|
||||
for subsequent reads. On -RED_ENOENT return, populated with
|
||||
the directory index immediately following the last valid
|
||||
one.
|
||||
@param pszName On successful return, populated with the name of the next
|
||||
directory entry. Buffer must be at least
|
||||
REDCONF_NAME_MAX + 1 in size, to store the maximum name
|
||||
length plus a null terminator.
|
||||
@param pulInode On successful return, populated with the inode number
|
||||
pointed at by the next directory entry.
|
||||
|
||||
@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_ENOENT There are no more entries in the directory.
|
||||
@retval -RED_ENOTDIR @p pPInode is not a directory.
|
||||
@retval -RED_EINVAL @p pPInode is not a mounted cached inode structure;
|
||||
or @p pszName is `NULL`; or @p pulIdx is `NULL`; or
|
||||
@p pulInode is `NULL`.
|
||||
*/
|
||||
REDSTATUS RedDirEntryRead(
|
||||
CINODE *pPInode,
|
||||
uint32_t *pulIdx,
|
||||
char *pszName,
|
||||
uint32_t *pulInode)
|
||||
{
|
||||
REDSTATUS ret = 0;
|
||||
|
||||
if(!CINODE_IS_MOUNTED(pPInode) || (pulIdx == NULL) || (pszName == NULL) || (pulInode == NULL))
|
||||
{
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(!pPInode->fDirectory)
|
||||
{
|
||||
ret = -RED_ENOTDIR;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulIdx = *pulIdx;
|
||||
uint32_t ulDirentCount = DirOffsetToEntryIndex(pPInode->pInodeBuf->ullSize);
|
||||
|
||||
/* Starting either at the beginning of the directory or where we left
|
||||
off, loop over the directory blocks, searching each block for a
|
||||
non-sparse dirent to return as the next entry in the directory.
|
||||
*/
|
||||
while((ret == 0) && (ulIdx < ulDirentCount))
|
||||
{
|
||||
uint32_t ulBlockOffset = ulIdx / DIRENTS_PER_BLOCK;
|
||||
|
||||
ret = RedInodeDataSeekAndRead(pPInode, ulBlockOffset);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
const DIRENT *pDirents = CAST_CONST_DIRENT_PTR(pPInode->pbData);
|
||||
uint32_t ulBlockLastIdx = REDMIN(DIRENTS_PER_BLOCK, ulDirentCount - (ulBlockOffset * DIRENTS_PER_BLOCK));
|
||||
uint32_t ulBlockIdx;
|
||||
|
||||
for(ulBlockIdx = ulIdx % DIRENTS_PER_BLOCK; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++)
|
||||
{
|
||||
if(pDirents[ulBlockIdx].ulInode != INODE_INVALID)
|
||||
{
|
||||
*pulIdx = ulIdx + 1U;
|
||||
RedStrNCpy(pszName, pDirents[ulBlockIdx].acName, REDCONF_NAME_MAX);
|
||||
pszName[REDCONF_NAME_MAX] = '\0';
|
||||
|
||||
*pulInode = pDirents[ulBlockIdx].ulInode;
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
*pulInode = RedRev32(*pulInode);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
ulIdx++;
|
||||
}
|
||||
|
||||
if(ulBlockIdx < ulBlockLastIdx)
|
||||
{
|
||||
REDASSERT(ulIdx <= ulDirentCount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(ret == -RED_ENODATA)
|
||||
{
|
||||
ulIdx += DIRENTS_PER_BLOCK - (ulIdx % DIRENTS_PER_BLOCK);
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Unexpected error, loop will terminate; nothing else to do.
|
||||
*/
|
||||
}
|
||||
|
||||
REDASSERT(ulIdx <= ulDirentCount);
|
||||
}
|
||||
|
||||
if((ret == 0) && (ulIdx >= ulDirentCount))
|
||||
{
|
||||
*pulIdx = ulDirentCount;
|
||||
ret = -RED_ENOENT;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1)
|
||||
/** Rename a directory entry.
|
||||
|
||||
@param pSrcPInode The inode of the directory containing @p pszSrcName.
|
||||
@param pszSrcName The name of the directory entry to be renamed.
|
||||
@param pSrcInode On successful return, populated with the inode of the
|
||||
source entry.
|
||||
@param pDstPInode The inode of the directory in which @p pszDstName will
|
||||
be created or replaced.
|
||||
@param pszDstName The name of the directory entry to be created or
|
||||
replaced.
|
||||
@param pDstInode On successful return, if the destination previously
|
||||
existed, populated with the inode previously pointed to
|
||||
by the destination. This may be the same as the source
|
||||
inode. If the destination did not exist, populated with
|
||||
INODE_INVALID.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EEXIST #REDCONF_RENAME_ATOMIC is false and the
|
||||
destination name exists.
|
||||
@retval -RED_EINVAL @p pSrcPInode is not a mounted dirty cached
|
||||
inode structure; or @p pSrcInode is `NULL`; or
|
||||
@p pszSrcName is not a valid name; or
|
||||
@p pDstPInode is not a mounted dirty cached
|
||||
inode structure; or @p pDstInode is `NULL`; or
|
||||
@p pszDstName is not a valid name.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
@retval -RED_EISDIR The destination name exists and is a directory,
|
||||
and the source name is a non-directory.
|
||||
@retval -RED_ENAMETOOLONG Either @p pszSrcName or @p pszDstName is longer
|
||||
than #REDCONF_NAME_MAX.
|
||||
@retval -RED_ENOENT The source name is not an existing entry; or
|
||||
either @p pszSrcName or @p pszDstName point to
|
||||
an empty string.
|
||||
@retval -RED_ENOTDIR @p pSrcPInode is not a directory; or
|
||||
@p pDstPInode is not a directory; or the source
|
||||
name is a directory and the destination name is
|
||||
a file.
|
||||
@retval -RED_ENOTEMPTY The destination name is a directory which is not
|
||||
empty.
|
||||
@retval -RED_ENOSPC The file system does not have enough space to
|
||||
extend the @p ulDstPInode directory.
|
||||
@retval -RED_EROFS The directory to be removed resides on a
|
||||
read-only file system.
|
||||
*/
|
||||
REDSTATUS RedDirEntryRename(
|
||||
CINODE *pSrcPInode,
|
||||
const char *pszSrcName,
|
||||
CINODE *pSrcInode,
|
||||
CINODE *pDstPInode,
|
||||
const char *pszDstName,
|
||||
CINODE *pDstInode)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if( !CINODE_IS_DIRTY(pSrcPInode)
|
||||
|| (pszSrcName == NULL)
|
||||
|| (pSrcInode == NULL)
|
||||
|| !CINODE_IS_DIRTY(pDstPInode)
|
||||
|| (pszDstName == NULL)
|
||||
|| (pDstInode == NULL))
|
||||
{
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(!pSrcPInode->fDirectory || !pDstPInode->fDirectory)
|
||||
{
|
||||
ret = -RED_ENOTDIR;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulDstIdx = 0U; /* Init'd to quiet warnings. */
|
||||
uint32_t ulSrcIdx;
|
||||
|
||||
/* Look up the source and destination names.
|
||||
*/
|
||||
ret = RedDirEntryLookup(pSrcPInode, pszSrcName, &ulSrcIdx, &pSrcInode->ulInode);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedDirEntryLookup(pDstPInode, pszDstName, &ulDstIdx, &pDstInode->ulInode);
|
||||
|
||||
if(ret == -RED_ENOENT)
|
||||
{
|
||||
if(ulDstIdx == DIR_INDEX_INVALID)
|
||||
{
|
||||
ret = -RED_ENOSPC;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if REDCONF_RENAME_ATOMIC == 1
|
||||
pDstInode->ulInode = INODE_INVALID;
|
||||
#endif
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
#if REDCONF_RENAME_ATOMIC == 0
|
||||
else if(ret == 0)
|
||||
{
|
||||
ret = -RED_EEXIST;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Nothing to do here, just propagate the error.
|
||||
*/
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if REDCONF_RENAME_ATOMIC == 1
|
||||
/* If both names point to the same inode, POSIX says to do nothing to
|
||||
either name.
|
||||
*/
|
||||
if((ret == 0) && (pSrcInode->ulInode != pDstInode->ulInode))
|
||||
#else
|
||||
if(ret == 0)
|
||||
#endif
|
||||
{
|
||||
ret = RedInodeMount(pSrcInode, FTYPE_EITHER, true);
|
||||
|
||||
#if REDCONF_RENAME_ATOMIC == 1
|
||||
if((ret == 0) && (pDstInode->ulInode != INODE_INVALID))
|
||||
{
|
||||
/* Source and destination must be the same type (file/dir).
|
||||
*/
|
||||
ret = RedInodeMount(pDstInode, pSrcInode->fDirectory ? FTYPE_DIR : FTYPE_FILE, true);
|
||||
|
||||
/* If renaming directories, the destination must be empty.
|
||||
*/
|
||||
if((ret == 0) && pDstInode->fDirectory && (pDstInode->pInodeBuf->ullSize > 0U))
|
||||
{
|
||||
ret = -RED_ENOTEMPTY;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* If we are renaming a directory, make sure the rename isn't
|
||||
cyclic (e.g., renaming "foo" into "foo/bar").
|
||||
*/
|
||||
if((ret == 0) && pSrcInode->fDirectory)
|
||||
{
|
||||
ret = DirCyclicRenameCheck(pSrcInode->ulInode, pDstPInode);
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = DirEntryWrite(pDstPInode, ulDstIdx, pSrcInode->ulInode, pszDstName, RedNameLen(pszDstName));
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedDirEntryDelete(pSrcPInode, ulSrcIdx);
|
||||
|
||||
if(ret == -RED_ENOSPC)
|
||||
{
|
||||
REDSTATUS ret2;
|
||||
|
||||
/* If there was not enough space to branch the parent
|
||||
directory inode and data block containin the source
|
||||
entry, revert destination directory entry to its
|
||||
previous state.
|
||||
*/
|
||||
#if REDCONF_RENAME_ATOMIC == 1
|
||||
if(pDstInode->ulInode != INODE_INVALID)
|
||||
{
|
||||
ret2 = DirEntryWrite(pDstPInode, ulDstIdx, pDstInode->ulInode, pszDstName, RedNameLen(pszDstName));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
ret2 = RedDirEntryDelete(pDstPInode, ulDstIdx);
|
||||
}
|
||||
|
||||
if(ret2 != 0)
|
||||
{
|
||||
ret = ret2;
|
||||
CRITICAL_ERROR();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
pSrcInode->pInodeBuf->ulPInode = pDstPInode->ulInode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** @brief Check for a cyclic rename.
|
||||
|
||||
A cyclic rename is renaming a directory into a subdirectory of itself. For
|
||||
example, renaming "a" into "a/b/c/d" is cyclic. These renames must not be
|
||||
allowed since they would corrupt the directory tree.
|
||||
|
||||
@param ulSrcInode The inode number of the directory being renamed.
|
||||
@param pDstPInode A pointer to the cached inode structure of the directory
|
||||
into which the source is being renamed.
|
||||
|
||||
@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 The rename is cyclic; or invalid parameters.
|
||||
@retval -RED_ENOTDIR @p pDstPInode is not a directory.
|
||||
*/
|
||||
static REDSTATUS DirCyclicRenameCheck(
|
||||
uint32_t ulSrcInode,
|
||||
const CINODE *pDstPInode)
|
||||
{
|
||||
REDSTATUS ret = 0;
|
||||
|
||||
if(!INODE_IS_VALID(ulSrcInode) || !CINODE_IS_MOUNTED(pDstPInode))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(ulSrcInode == pDstPInode->ulInode)
|
||||
{
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(!pDstPInode->fDirectory)
|
||||
{
|
||||
ret = -RED_ENOTDIR;
|
||||
}
|
||||
else
|
||||
{
|
||||
CINODE NextParent;
|
||||
/* Used to prevent infinite loop in case of corrupted directory
|
||||
structure.
|
||||
*/
|
||||
uint32_t ulIteration = 0U;
|
||||
|
||||
NextParent.ulInode = pDstPInode->pInodeBuf->ulPInode;
|
||||
|
||||
while( (NextParent.ulInode != ulSrcInode)
|
||||
&& (NextParent.ulInode != INODE_ROOTDIR)
|
||||
&& (NextParent.ulInode != INODE_INVALID)
|
||||
&& (ulIteration < gpRedVolConf->ulInodeCount))
|
||||
{
|
||||
ret = RedInodeMount(&NextParent, FTYPE_DIR, false);
|
||||
if(ret != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
NextParent.ulInode = NextParent.pInodeBuf->ulPInode;
|
||||
|
||||
RedInodePut(&NextParent, 0U);
|
||||
|
||||
ulIteration++;
|
||||
}
|
||||
|
||||
if((ret == 0) && (ulIteration == gpRedVolConf->ulInodeCount))
|
||||
{
|
||||
CRITICAL_ERROR();
|
||||
ret = -RED_EFUBAR;
|
||||
}
|
||||
|
||||
if((ret == 0) && (ulSrcInode == NextParent.ulInode))
|
||||
{
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1) */
|
||||
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
/** @brief Update the contents of a directory entry.
|
||||
|
||||
@param pPInode A pointer to the cached inode structure of the directory
|
||||
whose entry is being written.
|
||||
@param ulIdx The index of the directory entry to write.
|
||||
@param ulInode The inode number the directory entry is to point at.
|
||||
@param pszName The name of the directory entry.
|
||||
@param ulNameLen The length of @p pszName.
|
||||
|
||||
@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 There is not enough space on the volume to write the
|
||||
directory entry.
|
||||
@retval -RED_ENOTDIR @p pPInode is not a directory.
|
||||
@retval -RED_EINVAL Invalid parameters.
|
||||
*/
|
||||
static REDSTATUS DirEntryWrite(
|
||||
CINODE *pPInode,
|
||||
uint32_t ulIdx,
|
||||
uint32_t ulInode,
|
||||
const char *pszName,
|
||||
uint32_t ulNameLen)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if( !CINODE_IS_DIRTY(pPInode)
|
||||
|| (ulIdx >= DIRENTS_MAX)
|
||||
|| (!INODE_IS_VALID(ulInode) && (ulInode != INODE_INVALID))
|
||||
|| (pszName == NULL)
|
||||
|| (ulNameLen > REDCONF_NAME_MAX)
|
||||
|| ((ulNameLen == 0U) != (ulInode == INODE_INVALID)))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(!pPInode->fDirectory)
|
||||
{
|
||||
ret = -RED_ENOTDIR;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t ullOffset = DirEntryIndexToOffset(ulIdx);
|
||||
uint32_t ulLen = DIRENT_SIZE;
|
||||
static DIRENT de;
|
||||
|
||||
RedMemSet(&de, 0U, sizeof(de));
|
||||
|
||||
de.ulInode = ulInode;
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
de.ulInode = RedRev32(de.ulInode);
|
||||
#endif
|
||||
|
||||
RedStrNCpy(de.acName, pszName, ulNameLen);
|
||||
|
||||
ret = RedInodeDataWrite(pPInode, ullOffset, &ulLen, &de);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** @brief Convert a directory entry index to a byte offset.
|
||||
|
||||
@param ulIdx Directory entry index.
|
||||
|
||||
@return Byte offset in the directory corresponding with ulIdx.
|
||||
*/
|
||||
static uint64_t DirEntryIndexToOffset(
|
||||
uint32_t ulIdx)
|
||||
{
|
||||
uint32_t ulBlock = ulIdx / DIRENTS_PER_BLOCK;
|
||||
uint32_t ulOffsetInBlock = ulIdx % DIRENTS_PER_BLOCK;
|
||||
uint64_t ullOffset;
|
||||
|
||||
REDASSERT(ulIdx < DIRENTS_MAX);
|
||||
|
||||
ullOffset = (uint64_t)ulBlock << BLOCK_SIZE_P2;
|
||||
ullOffset += (uint64_t)ulOffsetInBlock * DIRENT_SIZE;
|
||||
|
||||
return ullOffset;
|
||||
}
|
||||
#endif /* REDCONF_READ_ONLY == 0 */
|
||||
|
||||
|
||||
/** @brief Convert a byte offset to a directory entry index.
|
||||
|
||||
@param ullOffset Byte offset in the directory.
|
||||
|
||||
@return Directory entry index corresponding with @p ullOffset.
|
||||
*/
|
||||
static uint32_t DirOffsetToEntryIndex(
|
||||
uint64_t ullOffset)
|
||||
{
|
||||
uint32_t ulIdx;
|
||||
|
||||
REDASSERT(ullOffset < INODE_SIZE_MAX);
|
||||
REDASSERT(((uint32_t)(ullOffset & (REDCONF_BLOCK_SIZE - 1U)) % DIRENT_SIZE) == 0U);
|
||||
|
||||
/* Avoid doing any 64-bit divides.
|
||||
*/
|
||||
ulIdx = (uint32_t)(ullOffset >> BLOCK_SIZE_P2) * DIRENTS_PER_BLOCK;
|
||||
ulIdx += (uint32_t)(ullOffset & (REDCONF_BLOCK_SIZE - 1U)) / DIRENT_SIZE;
|
||||
|
||||
return ulIdx;
|
||||
}
|
||||
|
||||
|
||||
#endif /* REDCONF_API_POSIX == 1 */
|
||||
|
@ -0,0 +1,235 @@
|
||||
/* ----> DO NOT REMOVE THE FOLLOWING NOTICE <----
|
||||
|
||||
Copyright (c) 2014-2015 Datalight, Inc.
|
||||
All Rights Reserved Worldwide.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; use version 2 of the License.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
/* Businesses and individuals that for commercial or other reasons cannot
|
||||
comply with the terms of the GPLv2 license may obtain a commercial license
|
||||
before incorporating Reliance Edge into proprietary software for
|
||||
distribution in any form. Visit http://www.datalight.com/reliance-edge for
|
||||
more information.
|
||||
*/
|
||||
/** @file
|
||||
@brief Implements the Reliance Edge file system formatter.
|
||||
*/
|
||||
#include <redfs.h>
|
||||
#include <redcoreapi.h>
|
||||
#include <redcore.h>
|
||||
|
||||
#if FORMAT_SUPPORTED
|
||||
|
||||
|
||||
/** @brief Format a file system volume.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EBUSY Volume is mounted.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
*/
|
||||
REDSTATUS RedVolFormat(void)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if(gpRedVolume->fMounted)
|
||||
{
|
||||
ret = -RED_EBUSY;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = RedOsBDevOpen(gbRedVolNum, BDEV_O_RDWR);
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
MASTERBLOCK *pMB;
|
||||
REDSTATUS ret2;
|
||||
|
||||
/* Overwrite the master block with zeroes, so that if formatting is
|
||||
interrupted, the volume will not be mountable.
|
||||
*/
|
||||
ret = RedBufferGet(BLOCK_NUM_MASTER, BFLAG_NEW | BFLAG_DIRTY, CAST_VOID_PTR_PTR(&pMB));
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedBufferFlush(BLOCK_NUM_MASTER, 1U);
|
||||
|
||||
RedBufferDiscard(pMB);
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedIoFlush(gbRedVolNum);
|
||||
}
|
||||
|
||||
#if REDCONF_IMAP_EXTERNAL == 1
|
||||
if((ret == 0) && !gpRedCoreVol->fImapInline)
|
||||
{
|
||||
uint32_t ulImapBlock;
|
||||
uint32_t ulImapBlockLimit = gpRedCoreVol->ulImapStartBN + (gpRedCoreVol->ulImapNodeCount * 2U);
|
||||
uint16_t uImapFlags = (uint16_t)((uint32_t)BFLAG_META_IMAP | BFLAG_NEW | BFLAG_DIRTY);
|
||||
|
||||
/* Technically it is only necessary to create one copy of each imap
|
||||
node (the copy the metaroot points at), but creating them both
|
||||
avoids headaches during disk image analysis from stale imaps
|
||||
left over from previous formats.
|
||||
*/
|
||||
for(ulImapBlock = gpRedCoreVol->ulImapStartBN; ulImapBlock < ulImapBlockLimit; ulImapBlock++)
|
||||
{
|
||||
IMAPNODE *pImap;
|
||||
|
||||
ret = RedBufferGet(ulImapBlock, uImapFlags, CAST_VOID_PTR_PTR(&pImap));
|
||||
if(ret != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
RedBufferPut(pImap);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Write the first metaroot.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
RedMemSet(gpRedMR, 0U, sizeof(*gpRedMR));
|
||||
|
||||
gpRedMR->ulFreeBlocks = gpRedVolume->ulBlocksAllocable;
|
||||
#if REDCONF_API_POSIX == 1
|
||||
gpRedMR->ulFreeInodes = gpRedVolConf->ulInodeCount;
|
||||
#endif
|
||||
gpRedMR->ulAllocNextBlock = gpRedCoreVol->ulFirstAllocableBN;
|
||||
|
||||
/* The branched flag is typically set automatically when bits in
|
||||
the imap change. It is set here explicitly because the imap has
|
||||
only been initialized, not changed.
|
||||
*/
|
||||
gpRedCoreVol->fBranched = true;
|
||||
|
||||
ret = RedVolTransact();
|
||||
}
|
||||
|
||||
#if REDCONF_API_POSIX == 1
|
||||
/* Create the root directory.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
CINODE rootdir;
|
||||
|
||||
rootdir.ulInode = INODE_ROOTDIR;
|
||||
ret = RedInodeCreate(&rootdir, INODE_INVALID, RED_S_IFDIR);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
RedInodePut(&rootdir, 0U);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if REDCONF_API_FSE == 1
|
||||
/* The FSE API does not support creating or deletes files, so all the
|
||||
inodes are created during setup.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
uint32_t ulInodeIdx;
|
||||
|
||||
for(ulInodeIdx = 0U; ulInodeIdx < gpRedVolConf->ulInodeCount; ulInodeIdx++)
|
||||
{
|
||||
CINODE ino;
|
||||
|
||||
ino.ulInode = INODE_FIRST_FREE + ulInodeIdx;
|
||||
ret = RedInodeCreate(&ino, INODE_INVALID, RED_S_IFREG);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
RedInodePut(&ino, 0U);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Write the second metaroot.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedVolTransact();
|
||||
}
|
||||
|
||||
/* Populate and write out the master block.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedBufferGet(BLOCK_NUM_MASTER, (uint16_t)((uint32_t)BFLAG_META_MASTER | BFLAG_NEW | BFLAG_DIRTY), CAST_VOID_PTR_PTR(&pMB));
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
pMB->ulVersion = RED_DISK_LAYOUT_VERSION;
|
||||
RedStrNCpy(pMB->acBuildNum, RED_BUILD_NUMBER, sizeof(pMB->acBuildNum));
|
||||
pMB->ulFormatTime = RedOsClockGetTime();
|
||||
pMB->ulInodeCount = gpRedVolConf->ulInodeCount;
|
||||
pMB->ulBlockCount = gpRedVolume->ulBlockCount;
|
||||
pMB->uMaxNameLen = REDCONF_NAME_MAX;
|
||||
pMB->uDirectPointers = REDCONF_DIRECT_POINTERS;
|
||||
pMB->uIndirectPointers = REDCONF_INDIRECT_POINTERS;
|
||||
pMB->bBlockSizeP2 = BLOCK_SIZE_P2;
|
||||
|
||||
#if REDCONF_API_POSIX == 1
|
||||
pMB->bFlags |= MBFLAG_API_POSIX;
|
||||
#endif
|
||||
#if REDCONF_INODE_TIMESTAMPS == 1
|
||||
pMB->bFlags |= MBFLAG_INODE_TIMESTAMPS;
|
||||
#endif
|
||||
#if REDCONF_INODE_BLOCKS == 1
|
||||
pMB->bFlags |= MBFLAG_INODE_BLOCKS;
|
||||
#endif
|
||||
#if (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_LINK == 1)
|
||||
pMB->bFlags |= MBFLAG_INODE_NLINK;
|
||||
#endif
|
||||
|
||||
ret = RedBufferFlush(BLOCK_NUM_MASTER, 1U);
|
||||
|
||||
RedBufferPut(pMB);
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedIoFlush(gbRedVolNum);
|
||||
}
|
||||
|
||||
ret2 = RedOsBDevClose(gbRedVolNum);
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = ret2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Discard the buffers so a subsequent format will not run into blocks it
|
||||
does not expect.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedBufferDiscardRange(0U, gpRedVolume->ulBlockCount);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#endif /* FORMAT_SUPPORTED */
|
||||
|
@ -0,0 +1,349 @@
|
||||
/* ----> 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 allocation routines.
|
||||
|
||||
This module implements routines for working with the imap, a bitmap which
|
||||
tracks which blocks are allocated or free. Some of the functionality is
|
||||
delegated to imapinline.c and imapextern.c.
|
||||
*/
|
||||
#include <redfs.h>
|
||||
#include <redcore.h>
|
||||
|
||||
|
||||
/** @brief Get the allocation bit of a block from either metaroot.
|
||||
|
||||
Will pass the call down either to the inline imap or to the external imap
|
||||
implementation, whichever is appropriate for the current volume.
|
||||
|
||||
@param bMR The metaroot index: either 0 or 1.
|
||||
@param ulBlock The block number to query.
|
||||
@param pfAllocated On successful return, populated with the allocation bit
|
||||
of the block.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p bMR is out of range; or @p ulBlock is out of range;
|
||||
or @p pfAllocated is `NULL`.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
*/
|
||||
REDSTATUS RedImapBlockGet(
|
||||
uint8_t bMR,
|
||||
uint32_t ulBlock,
|
||||
bool *pfAllocated)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if( (bMR > 1U)
|
||||
|| (ulBlock < gpRedCoreVol->ulInodeTableStartBN)
|
||||
|| (ulBlock >= gpRedVolume->ulBlockCount)
|
||||
|| (pfAllocated == NULL))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if (REDCONF_IMAP_INLINE == 1) && (REDCONF_IMAP_EXTERNAL == 1)
|
||||
if(gpRedCoreVol->fImapInline)
|
||||
{
|
||||
ret = RedImapIBlockGet(bMR, ulBlock, pfAllocated);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = RedImapEBlockGet(bMR, ulBlock, pfAllocated);
|
||||
}
|
||||
#elif REDCONF_IMAP_INLINE == 1
|
||||
ret = RedImapIBlockGet(bMR, ulBlock, pfAllocated);
|
||||
#else
|
||||
ret = RedImapEBlockGet(bMR, ulBlock, pfAllocated);
|
||||
#endif
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
/** @brief Set the allocation bit of a block in the working metaroot.
|
||||
|
||||
Will pass the call down either to the inline imap or to the external imap
|
||||
implementation, whichever is appropriate for the current volume.
|
||||
|
||||
@param ulBlock The block number to allocate or free.
|
||||
@param fAllocated Whether to allocate the block (true) or free it (false).
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p ulBlock is out of range; or @p ulBlock is allocable
|
||||
and @p fAllocated is 1.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
*/
|
||||
REDSTATUS RedImapBlockSet(
|
||||
uint32_t ulBlock,
|
||||
bool fAllocated)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if( (ulBlock < gpRedCoreVol->ulInodeTableStartBN)
|
||||
|| (ulBlock >= gpRedVolume->ulBlockCount))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if( (ulBlock >= gpRedCoreVol->ulFirstAllocableBN)
|
||||
&& ( (fAllocated && (gpRedMR->ulFreeBlocks == 0U))
|
||||
|| ((!fAllocated) && (gpRedMR->ulFreeBlocks >= gpRedVolume->ulBlocksAllocable))))
|
||||
{
|
||||
/* Attempting either to free more blocks than are allocable, or
|
||||
allocate a block when there are none available. This could indicate
|
||||
metadata corruption.
|
||||
*/
|
||||
CRITICAL_ERROR();
|
||||
ret = -RED_EFUBAR;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if (REDCONF_IMAP_INLINE == 1) && (REDCONF_IMAP_EXTERNAL == 1)
|
||||
if(gpRedCoreVol->fImapInline)
|
||||
{
|
||||
ret = RedImapIBlockSet(ulBlock, fAllocated);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = RedImapEBlockSet(ulBlock, fAllocated);
|
||||
}
|
||||
#elif REDCONF_IMAP_INLINE == 1
|
||||
ret = RedImapIBlockSet(ulBlock, fAllocated);
|
||||
#else
|
||||
ret = RedImapEBlockSet(ulBlock, fAllocated);
|
||||
#endif
|
||||
|
||||
/* Any change to the allocation state of a block indicates that the
|
||||
volume is now branched.
|
||||
*/
|
||||
gpRedCoreVol->fBranched = true;
|
||||
}
|
||||
|
||||
/* If a block was marked as no longer in use, discard it from the buffers.
|
||||
*/
|
||||
if((ret == 0) && (!fAllocated))
|
||||
{
|
||||
ret = RedBufferDiscardRange(ulBlock, 1U);
|
||||
CRITICAL_ASSERT(ret == 0);
|
||||
}
|
||||
|
||||
/* Adjust the free/almost free block count if the block was allocable.
|
||||
Discard the block if required.
|
||||
*/
|
||||
if((ret == 0) && (ulBlock >= gpRedCoreVol->ulFirstAllocableBN))
|
||||
{
|
||||
if(fAllocated)
|
||||
{
|
||||
gpRedMR->ulFreeBlocks--;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool fWasAllocated;
|
||||
|
||||
/* Whether the block became free or almost free depends on its
|
||||
previous allocation state. If it was used, then it is now
|
||||
almost free. Otherwise, it was new and is now free.
|
||||
*/
|
||||
ret = RedImapBlockGet(1U - gpRedCoreVol->bCurMR, ulBlock, &fWasAllocated);
|
||||
CRITICAL_ASSERT(ret == 0);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
if(fWasAllocated)
|
||||
{
|
||||
gpRedCoreVol->ulAlmostFreeBlocks++;
|
||||
}
|
||||
else
|
||||
{
|
||||
gpRedMR->ulFreeBlocks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** @brief Allocate one block.
|
||||
|
||||
@param pulBlock On successful return, populated with the allocated block
|
||||
number.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p pulBlock is `NULL`.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
@retval -RED_ENOSPC Insufficient free space to perform the allocation.
|
||||
*/
|
||||
REDSTATUS RedImapAllocBlock(
|
||||
uint32_t *pulBlock)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if(pulBlock == NULL)
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(gpRedMR->ulFreeBlocks == 0U)
|
||||
{
|
||||
ret = -RED_ENOSPC;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulStopBlock = gpRedMR->ulAllocNextBlock;
|
||||
bool fAllocated = false;
|
||||
|
||||
do
|
||||
{
|
||||
ALLOCSTATE state;
|
||||
|
||||
ret = RedImapBlockState(gpRedMR->ulAllocNextBlock, &state);
|
||||
CRITICAL_ASSERT(ret == 0);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
if(state == ALLOCSTATE_FREE)
|
||||
{
|
||||
ret = RedImapBlockSet(gpRedMR->ulAllocNextBlock, true);
|
||||
CRITICAL_ASSERT(ret == 0);
|
||||
|
||||
*pulBlock = gpRedMR->ulAllocNextBlock;
|
||||
fAllocated = true;
|
||||
}
|
||||
|
||||
/* Increment the next block number, wrapping it when the end of
|
||||
the volume is reached.
|
||||
*/
|
||||
gpRedMR->ulAllocNextBlock++;
|
||||
if(gpRedMR->ulAllocNextBlock == gpRedVolume->ulBlockCount)
|
||||
{
|
||||
gpRedMR->ulAllocNextBlock = gpRedCoreVol->ulFirstAllocableBN;
|
||||
}
|
||||
}
|
||||
}
|
||||
while((ret == 0) && !fAllocated && (gpRedMR->ulAllocNextBlock != ulStopBlock));
|
||||
|
||||
if((ret == 0) && !fAllocated)
|
||||
{
|
||||
/* The free block count was already determined to be non-zero, no
|
||||
error occurred while looking for free blocks, but no free blocks
|
||||
were found. This indicates metadata corruption.
|
||||
*/
|
||||
CRITICAL_ERROR();
|
||||
ret = -RED_EFUBAR;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* REDCONF_READ_ONLY == 0 */
|
||||
|
||||
|
||||
/** @brief Get the allocation state of a block.
|
||||
|
||||
Takes into account the allocation bits from both metaroots, and returns one
|
||||
of four possible allocation state values:
|
||||
|
||||
- ::ALLOCSTATE_FREE Free and may be allocated; writeable.
|
||||
- ::ALLOCSTATE_USED In-use and transacted; not writeable.
|
||||
- ::ALLOCSTATE_NEW In-use but not transacted; writeable.
|
||||
- ::ALLOCSTATE_AFREE Will become free after a transaction; not writeable.
|
||||
|
||||
@param ulBlock The block number to query.
|
||||
@param pState On successful return, populated with the state of the block.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p ulBlock is out of range; or @p pState is `NULL`.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
*/
|
||||
REDSTATUS RedImapBlockState(
|
||||
uint32_t ulBlock,
|
||||
ALLOCSTATE *pState)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if( (ulBlock < gpRedCoreVol->ulInodeTableStartBN)
|
||||
|| (ulBlock >= gpRedVolume->ulBlockCount)
|
||||
|| (pState == NULL))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool fBitCurrent;
|
||||
|
||||
ret = RedImapBlockGet(gpRedCoreVol->bCurMR, ulBlock, &fBitCurrent);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
bool fBitOld;
|
||||
|
||||
ret = RedImapBlockGet(1U - gpRedCoreVol->bCurMR, ulBlock, &fBitOld);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
if(fBitCurrent)
|
||||
{
|
||||
if(fBitOld)
|
||||
{
|
||||
*pState = ALLOCSTATE_USED;
|
||||
}
|
||||
else
|
||||
{
|
||||
*pState = ALLOCSTATE_NEW;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(fBitOld)
|
||||
{
|
||||
*pState = ALLOCSTATE_AFREE;
|
||||
}
|
||||
else
|
||||
{
|
||||
*pState = ALLOCSTATE_FREE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -0,0 +1,316 @@
|
||||
/* ----> 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 routines for the external imap.
|
||||
|
||||
The external imap is used on volumes that are too big for the imap bitmap
|
||||
to be stored entirely in the metaroot, so instead the bitmap is stored in
|
||||
imap nodes on disk, and the metaroot bitmap is used to toggle between imap
|
||||
nodes.
|
||||
*/
|
||||
#include <redfs.h>
|
||||
|
||||
#if REDCONF_IMAP_EXTERNAL == 1
|
||||
|
||||
#include <redcore.h>
|
||||
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
static REDSTATUS ImapNodeBranch(uint32_t ulImapNode, IMAPNODE **ppImap);
|
||||
static bool ImapNodeIsBranched(uint32_t ulImapNode);
|
||||
#endif
|
||||
|
||||
|
||||
/** @brief Get the allocation bit of a block from the imap as it exists in
|
||||
either metaroot.
|
||||
|
||||
@param bMR The metaroot index: either 0 or 1.
|
||||
@param ulBlock The block number to query.
|
||||
@param pfAllocated On successful exit, populated with the allocation bit
|
||||
of the block.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p bMR is out of range; or @p ulBlock is out of range;
|
||||
or @p pfAllocated is `NULL`.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
*/
|
||||
REDSTATUS RedImapEBlockGet(
|
||||
uint8_t bMR,
|
||||
uint32_t ulBlock,
|
||||
bool *pfAllocated)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if( gpRedCoreVol->fImapInline
|
||||
|| (bMR > 1U)
|
||||
|| (ulBlock < gpRedCoreVol->ulInodeTableStartBN)
|
||||
|| (ulBlock >= gpRedVolume->ulBlockCount)
|
||||
|| (pfAllocated == NULL))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulOffset = ulBlock - gpRedCoreVol->ulInodeTableStartBN;
|
||||
uint32_t ulImapNode = ulOffset / IMAPNODE_ENTRIES;
|
||||
uint8_t bMRToRead = bMR;
|
||||
IMAPNODE *pImap;
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
/* If the imap node is not branched, then both copies of the imap are
|
||||
identical. If the old metaroot copy is requested, use the current
|
||||
copy instead, since it is more likely to be buffered.
|
||||
*/
|
||||
if(bMR == (1U - gpRedCoreVol->bCurMR))
|
||||
{
|
||||
if(!ImapNodeIsBranched(ulImapNode))
|
||||
{
|
||||
bMRToRead = 1U - bMR;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = RedBufferGet(RedImapNodeBlock(bMRToRead, ulImapNode), BFLAG_META_IMAP, CAST_VOID_PTR_PTR(&pImap));
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
*pfAllocated = RedBitGet(pImap->abEntries, ulOffset % IMAPNODE_ENTRIES);
|
||||
|
||||
RedBufferPut(pImap);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
/** @brief Set the allocation bit of a block in the working-state imap.
|
||||
|
||||
@param ulBlock The block number to allocate or free.
|
||||
@param fAllocated Whether to allocate the block (true) or free it (false).
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p ulBlock is out of range.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
*/
|
||||
REDSTATUS RedImapEBlockSet(
|
||||
uint32_t ulBlock,
|
||||
bool fAllocated)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if( gpRedCoreVol->fImapInline
|
||||
|| (ulBlock < gpRedCoreVol->ulInodeTableStartBN)
|
||||
|| (ulBlock >= gpRedVolume->ulBlockCount))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulOffset = ulBlock - gpRedCoreVol->ulInodeTableStartBN;
|
||||
uint32_t ulImapNode = ulOffset / IMAPNODE_ENTRIES;
|
||||
IMAPNODE *pImap;
|
||||
|
||||
ret = ImapNodeBranch(ulImapNode, &pImap);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
uint32_t ulImapEntry = ulOffset % IMAPNODE_ENTRIES;
|
||||
|
||||
if(RedBitGet(pImap->abEntries, ulImapEntry) == fAllocated)
|
||||
{
|
||||
/* The driver shouldn't ever set a bit in the imap to its
|
||||
current value. That shouldn't ever be needed, and it
|
||||
indicates that the driver is doing unnecessary I/O, or
|
||||
that the imap is corrupt.
|
||||
*/
|
||||
CRITICAL_ERROR();
|
||||
ret = -RED_EFUBAR;
|
||||
}
|
||||
else if(fAllocated)
|
||||
{
|
||||
RedBitSet(pImap->abEntries, ulImapEntry);
|
||||
}
|
||||
else
|
||||
{
|
||||
RedBitClear(pImap->abEntries, ulImapEntry);
|
||||
}
|
||||
|
||||
RedBufferPut(pImap);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** @brief Branch an imap node and get a buffer for it.
|
||||
|
||||
If the imap node is already branched, it can be overwritten in its current
|
||||
location, and this function just gets it buffered dirty. If the node is not
|
||||
already branched, the metaroot must be updated to toggle the imap node to
|
||||
its alternate location, thereby preserving the committed state copy of the
|
||||
imap node.
|
||||
|
||||
@param ulImapNode The imap node to branch and buffer.
|
||||
@param ppImap On successful return, populated with the imap node
|
||||
buffer, which will be marked dirty.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p ulImapNode is out of range; or @p ppImap is `NULL`.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
*/
|
||||
static REDSTATUS ImapNodeBranch(
|
||||
uint32_t ulImapNode,
|
||||
IMAPNODE **ppImap)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if((ulImapNode >= gpRedCoreVol->ulImapNodeCount) || (ppImap == NULL))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else if(ImapNodeIsBranched(ulImapNode))
|
||||
{
|
||||
/* Imap node is already branched, so just get it buffered dirty.
|
||||
*/
|
||||
ret = RedBufferGet(RedImapNodeBlock(gpRedCoreVol->bCurMR, ulImapNode), BFLAG_META_IMAP | BFLAG_DIRTY, CAST_VOID_PTR_PTR(ppImap));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulBlockCurrent;
|
||||
uint32_t ulBlockOld;
|
||||
|
||||
/* The metaroot currently points to the committed state imap node.
|
||||
Toggle the metaroot to point at the alternate, writeable location.
|
||||
*/
|
||||
if(RedBitGet(gpRedMR->abEntries, ulImapNode))
|
||||
{
|
||||
RedBitClear(gpRedMR->abEntries, ulImapNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
RedBitSet(gpRedMR->abEntries, ulImapNode);
|
||||
}
|
||||
|
||||
ulBlockCurrent = RedImapNodeBlock(gpRedCoreVol->bCurMR, ulImapNode);
|
||||
ulBlockOld = RedImapNodeBlock(1U - gpRedCoreVol->bCurMR, ulImapNode);
|
||||
|
||||
ret = RedBufferDiscardRange(ulBlockCurrent, 1U);
|
||||
|
||||
/* Buffer the committed copy then reassign the block number to the
|
||||
writeable location. This also dirties the buffer.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedBufferGet(ulBlockOld, BFLAG_META_IMAP, CAST_VOID_PTR_PTR(ppImap));
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
RedBufferBranch(*ppImap, ulBlockCurrent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** @brief Determine whether an imap node is branched.
|
||||
|
||||
If the imap node is branched, it can be overwritten in its current location.
|
||||
|
||||
@param ulImapNode The imap node to examine.
|
||||
|
||||
@return Whether the imap node is branched.
|
||||
*/
|
||||
static bool ImapNodeIsBranched(
|
||||
uint32_t ulImapNode)
|
||||
{
|
||||
bool fNodeBitSetInMetaroot0 = RedBitGet(gpRedCoreVol->aMR[0U].abEntries, ulImapNode);
|
||||
bool fNodeBitSetInMetaroot1 = RedBitGet(gpRedCoreVol->aMR[1U].abEntries, ulImapNode);
|
||||
|
||||
/* If the imap node is not branched, both metaroots will point to the same
|
||||
copy of the node.
|
||||
*/
|
||||
return fNodeBitSetInMetaroot0 != fNodeBitSetInMetaroot1;
|
||||
}
|
||||
#endif /* REDCONF_READ_ONLY == 0 */
|
||||
|
||||
|
||||
/** @brief Calculate the block number of the imap node location indicated by the
|
||||
given metaroot.
|
||||
|
||||
An imap node has two locations on disk. A bit in the metaroot bitmap
|
||||
indicates which location is the valid one, according to that metaroot. This
|
||||
function returns the block number of the imap node which is valid in the
|
||||
given metaroot.
|
||||
|
||||
@param bMR Which metaroot to examine.
|
||||
@param ulImapNode The imap node for which to calculate the block number.
|
||||
|
||||
@return Block number of the imap node, as indicated by the given metaroot.
|
||||
*/
|
||||
uint32_t RedImapNodeBlock(
|
||||
uint8_t bMR,
|
||||
uint32_t ulImapNode)
|
||||
{
|
||||
uint32_t ulBlock;
|
||||
|
||||
REDASSERT(ulImapNode < gpRedCoreVol->ulImapNodeCount);
|
||||
|
||||
ulBlock = gpRedCoreVol->ulImapStartBN + (ulImapNode * 2U);
|
||||
|
||||
if(bMR > 1U)
|
||||
{
|
||||
REDERROR();
|
||||
}
|
||||
else if(RedBitGet(gpRedCoreVol->aMR[bMR].abEntries, ulImapNode))
|
||||
{
|
||||
/* Bit is set, so point ulBlock at the second copy of the node.
|
||||
*/
|
||||
ulBlock++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* ulBlock already points at the first copy of the node.
|
||||
*/
|
||||
}
|
||||
|
||||
return ulBlock;
|
||||
}
|
||||
|
||||
#endif /* REDCONF_IMAP_EXTERNAL == 1 */
|
||||
|
@ -0,0 +1,133 @@
|
||||
/* ----> 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 routines for the inline imap.
|
||||
|
||||
The inline imap is used on volumes that are small enough for the imap bitmap
|
||||
to be entirely contained within the metaroot.
|
||||
*/
|
||||
#include <redfs.h>
|
||||
|
||||
#if REDCONF_IMAP_INLINE == 1
|
||||
|
||||
#include <redcore.h>
|
||||
|
||||
|
||||
/** @brief Get the allocation bit of a block from either metaroot.
|
||||
|
||||
@param bMR The metaroot index: either 0 or 1.
|
||||
@param ulBlock The block number to query.
|
||||
@param pfAllocated On successful return, populated with the allocation bit
|
||||
of the block.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p bMR is out of range; or @p ulBlock is out of range;
|
||||
@p pfAllocated is `NULL`; or the current volume does not
|
||||
use the inline imap.
|
||||
*/
|
||||
REDSTATUS RedImapIBlockGet(
|
||||
uint8_t bMR,
|
||||
uint32_t ulBlock,
|
||||
bool *pfAllocated)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if( (!gpRedCoreVol->fImapInline)
|
||||
|| (bMR > 1U)
|
||||
|| (ulBlock < gpRedCoreVol->ulInodeTableStartBN)
|
||||
|| (ulBlock >= gpRedVolume->ulBlockCount)
|
||||
|| (pfAllocated == NULL))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
*pfAllocated = RedBitGet(gpRedCoreVol->aMR[bMR].abEntries, ulBlock - gpRedCoreVol->ulInodeTableStartBN);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
/** @brief Set the allocation bit of a block in the working metaroot.
|
||||
|
||||
@param ulBlock The block number to allocate or free.
|
||||
@param fAllocated Whether to allocate the block (true) or free it (false).
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL @p ulBlock is out of range; or the current volume does
|
||||
not use the inline imap.
|
||||
*/
|
||||
REDSTATUS RedImapIBlockSet(
|
||||
uint32_t ulBlock,
|
||||
bool fAllocated)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if( (!gpRedCoreVol->fImapInline)
|
||||
|| (ulBlock < gpRedCoreVol->ulInodeTableStartBN)
|
||||
|| (ulBlock >= gpRedVolume->ulBlockCount))
|
||||
{
|
||||
REDERROR();
|
||||
ret = -RED_EINVAL;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t ulOffset = ulBlock - gpRedCoreVol->ulInodeTableStartBN;
|
||||
|
||||
if(RedBitGet(gpRedMR->abEntries, ulOffset) == fAllocated)
|
||||
{
|
||||
/* The driver shouldn't ever set a bit in the imap to its current
|
||||
value. This is more of a problem with the external imap, but it
|
||||
is checked here for consistency.
|
||||
*/
|
||||
CRITICAL_ERROR();
|
||||
ret = -RED_EFUBAR;
|
||||
}
|
||||
else if(fAllocated)
|
||||
{
|
||||
RedBitSet(gpRedMR->abEntries, ulOffset);
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
RedBitClear(gpRedMR->abEntries, ulOffset);
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* REDCONF_IMAP_INLINE == 1 */
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,540 @@
|
||||
/* ----> 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 core volume operations.
|
||||
*/
|
||||
#include <redfs.h>
|
||||
#include <redcore.h>
|
||||
|
||||
|
||||
static bool MetarootIsValid(METAROOT *pMR, bool *pfSectorCRCIsValid);
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
static void MetaRootEndianSwap(METAROOT *pMetaRoot);
|
||||
#endif
|
||||
|
||||
|
||||
/** @brief Mount a file system volume.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EIO Volume not formatted, improperly formatted, or corrupt.
|
||||
*/
|
||||
REDSTATUS RedVolMount(void)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
ret = RedOsBDevOpen(gbRedVolNum, BDEV_O_RDWR);
|
||||
#else
|
||||
ret = RedOsBDevOpen(gbRedVolNum, BDEV_O_RDONLY);
|
||||
#endif
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedVolMountMaster();
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedVolMountMetaroot();
|
||||
}
|
||||
|
||||
if(ret != 0)
|
||||
{
|
||||
/* If we fail to mount, invalidate the buffers to prevent any
|
||||
confusion that could be caused by stale or corrupt metadata.
|
||||
*/
|
||||
(void)RedBufferDiscardRange(0U, gpRedVolume->ulBlockCount);
|
||||
(void)RedOsBDevClose(gbRedVolNum);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** @brief Mount the master block.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EIO Master block missing, corrupt, or inconsistent with the
|
||||
compile-time driver settings.
|
||||
*/
|
||||
REDSTATUS RedVolMountMaster(void)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
MASTERBLOCK *pMB;
|
||||
|
||||
/* Read the master block, to ensure that the disk was formatted with
|
||||
Reliance Edge.
|
||||
*/
|
||||
ret = RedBufferGet(BLOCK_NUM_MASTER, BFLAG_META_MASTER, CAST_VOID_PTR_PTR(&pMB));
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
/* Verify that the driver was compiled with the same settings that
|
||||
the disk was formatted with. If not, the user has made a
|
||||
mistake: either the driver settings are wrong, or the disk needs
|
||||
to be reformatted.
|
||||
*/
|
||||
if( (pMB->ulVersion != RED_DISK_LAYOUT_VERSION)
|
||||
|| (pMB->ulInodeCount != gpRedVolConf->ulInodeCount)
|
||||
|| (pMB->ulBlockCount != gpRedVolume->ulBlockCount)
|
||||
|| (pMB->uMaxNameLen != REDCONF_NAME_MAX)
|
||||
|| (pMB->uDirectPointers != REDCONF_DIRECT_POINTERS)
|
||||
|| (pMB->uIndirectPointers != REDCONF_INDIRECT_POINTERS)
|
||||
|| (pMB->bBlockSizeP2 != BLOCK_SIZE_P2)
|
||||
|| (((pMB->bFlags & MBFLAG_API_POSIX) != 0U) != (REDCONF_API_POSIX == 1))
|
||||
|| (((pMB->bFlags & MBFLAG_INODE_TIMESTAMPS) != 0U) != (REDCONF_INODE_TIMESTAMPS == 1))
|
||||
|| (((pMB->bFlags & MBFLAG_INODE_BLOCKS) != 0U) != (REDCONF_INODE_BLOCKS == 1)))
|
||||
{
|
||||
ret = -RED_EIO;
|
||||
}
|
||||
#if REDCONF_API_POSIX == 1
|
||||
else if(((pMB->bFlags & MBFLAG_INODE_NLINK) != 0U) != (REDCONF_API_POSIX_LINK == 1))
|
||||
{
|
||||
ret = -RED_EIO;
|
||||
}
|
||||
#else
|
||||
else if((pMB->bFlags & MBFLAG_INODE_NLINK) != 0U)
|
||||
{
|
||||
ret = -RED_EIO;
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
/* Master block configuration is valid.
|
||||
|
||||
Save the sequence number of the master block in the volume,
|
||||
since we need it later (see RedVolMountMetaroot()) and we do
|
||||
not want to re-buffer the master block.
|
||||
*/
|
||||
gpRedVolume->ullSequence = pMB->hdr.ullSequence;
|
||||
}
|
||||
|
||||
RedBufferPut(pMB);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** @brief Mount the latest metaroot.
|
||||
|
||||
This function also populates the volume contexts.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EIO Both metaroots are missing or corrupt.
|
||||
*/
|
||||
REDSTATUS RedVolMountMetaroot(void)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
ret = RedIoRead(gbRedVolNum, BLOCK_NUM_FIRST_METAROOT, 1U, &gpRedCoreVol->aMR[0U]);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedIoRead(gbRedVolNum, BLOCK_NUM_FIRST_METAROOT + 1U, 1U, &gpRedCoreVol->aMR[1U]);
|
||||
}
|
||||
|
||||
/* Determine which metaroot is the most recent copy that was written
|
||||
completely.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
uint8_t bMR = UINT8_MAX;
|
||||
bool fSectorCRCIsValid;
|
||||
|
||||
if(MetarootIsValid(&gpRedCoreVol->aMR[0U], &fSectorCRCIsValid))
|
||||
{
|
||||
bMR = 0U;
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
MetaRootEndianSwap(&gpRedCoreVol->aMR[0U]);
|
||||
#endif
|
||||
}
|
||||
else if(gpRedVolConf->fAtomicSectorWrite && !fSectorCRCIsValid)
|
||||
{
|
||||
ret = -RED_EIO;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Metaroot is not valid, so it is ignored and there's nothing
|
||||
to do here.
|
||||
*/
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
if(MetarootIsValid(&gpRedCoreVol->aMR[1U], &fSectorCRCIsValid))
|
||||
{
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
MetaRootEndianSwap(&gpRedCoreVol->aMR[1U]);
|
||||
#endif
|
||||
|
||||
if((bMR != 0U) || (gpRedCoreVol->aMR[1U].hdr.ullSequence > gpRedCoreVol->aMR[0U].hdr.ullSequence))
|
||||
{
|
||||
bMR = 1U;
|
||||
}
|
||||
}
|
||||
else if(gpRedVolConf->fAtomicSectorWrite && !fSectorCRCIsValid)
|
||||
{
|
||||
ret = -RED_EIO;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Metaroot is not valid, so it is ignored and there's nothing
|
||||
to do here.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
if(bMR == UINT8_MAX)
|
||||
{
|
||||
/* Neither metaroot was valid.
|
||||
*/
|
||||
ret = -RED_EIO;
|
||||
}
|
||||
else
|
||||
{
|
||||
gpRedCoreVol->bCurMR = bMR;
|
||||
gpRedMR = &gpRedCoreVol->aMR[bMR];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
/* Normally the metaroot contains the highest sequence number, but the
|
||||
master block is the last block written during format, so on a
|
||||
freshly formatted volume the master block sequence number (stored in
|
||||
gpRedVolume->ullSequence) will be higher than that in the metaroot.
|
||||
*/
|
||||
if(gpRedMR->hdr.ullSequence > gpRedVolume->ullSequence)
|
||||
{
|
||||
gpRedVolume->ullSequence = gpRedMR->hdr.ullSequence;
|
||||
}
|
||||
|
||||
/* gpRedVolume->ullSequence stores the *next* sequence number; to avoid
|
||||
giving the next node written to disk the same sequence number as the
|
||||
metaroot, increment it here.
|
||||
*/
|
||||
ret = RedVolSeqNumIncrement();
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
gpRedVolume->fMounted = true;
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
gpRedVolume->fReadOnly = false;
|
||||
#endif
|
||||
|
||||
#if RESERVED_BLOCKS > 0U
|
||||
gpRedCoreVol->fUseReservedBlocks = false;
|
||||
#endif
|
||||
gpRedCoreVol->ulAlmostFreeBlocks = 0U;
|
||||
|
||||
gpRedCoreVol->aMR[1U - gpRedCoreVol->bCurMR] = *gpRedMR;
|
||||
gpRedCoreVol->bCurMR = 1U - gpRedCoreVol->bCurMR;
|
||||
gpRedMR = &gpRedCoreVol->aMR[gpRedCoreVol->bCurMR];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/** @brief Determine whether the metaroot is valid.
|
||||
|
||||
@param pMR The metaroot buffer.
|
||||
@param pfSectorCRCIsValid Populated with whether the first sector of the
|
||||
metaroot buffer is valid.
|
||||
|
||||
@return Whether the metaroot is valid.
|
||||
|
||||
@retval true The metaroot buffer is valid.
|
||||
@retval false The metaroot buffer is invalid.
|
||||
*/
|
||||
static bool MetarootIsValid(
|
||||
METAROOT *pMR,
|
||||
bool *pfSectorCRCIsValid)
|
||||
{
|
||||
bool fRet = false;
|
||||
|
||||
if(pfSectorCRCIsValid == NULL)
|
||||
{
|
||||
REDERROR();
|
||||
}
|
||||
else if(pMR == NULL)
|
||||
{
|
||||
REDERROR();
|
||||
*pfSectorCRCIsValid = false;
|
||||
}
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
else if(RedRev32(pMR->hdr.ulSignature) != META_SIG_METAROOT)
|
||||
#else
|
||||
else if(pMR->hdr.ulSignature != META_SIG_METAROOT)
|
||||
#endif
|
||||
{
|
||||
*pfSectorCRCIsValid = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint8_t *pbMR = CAST_VOID_PTR_TO_CONST_UINT8_PTR(pMR);
|
||||
uint32_t ulSectorCRC = pMR->ulSectorCRC;
|
||||
uint32_t ulCRC;
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
ulSectorCRC = RedRev32(ulSectorCRC);
|
||||
#endif
|
||||
|
||||
/* The sector CRC was zero when the CRC was computed during the
|
||||
transaction, so it must be zero here.
|
||||
*/
|
||||
pMR->ulSectorCRC = 0U;
|
||||
|
||||
ulCRC = RedCrc32Update(0U, &pbMR[8U], gpRedVolConf->ulSectorSize - 8U);
|
||||
|
||||
fRet = ulCRC == ulSectorCRC;
|
||||
*pfSectorCRCIsValid = fRet;
|
||||
|
||||
if(fRet)
|
||||
{
|
||||
if(gpRedVolConf->ulSectorSize < REDCONF_BLOCK_SIZE)
|
||||
{
|
||||
ulCRC = RedCrc32Update(ulCRC, &pbMR[gpRedVolConf->ulSectorSize], REDCONF_BLOCK_SIZE - gpRedVolConf->ulSectorSize);
|
||||
}
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
ulCRC = RedRev32(ulCRC);
|
||||
#endif
|
||||
|
||||
fRet = ulCRC == pMR->hdr.ulCRC;
|
||||
}
|
||||
}
|
||||
|
||||
return fRet;
|
||||
}
|
||||
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
/** @brief Commit a transaction point.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EIO A disk I/O error occurred.
|
||||
*/
|
||||
REDSTATUS RedVolTransact(void)
|
||||
{
|
||||
REDSTATUS ret = 0;
|
||||
|
||||
REDASSERT(!gpRedVolume->fReadOnly); /* Should be checked by caller. */
|
||||
|
||||
if(gpRedCoreVol->fBranched)
|
||||
{
|
||||
gpRedMR->ulFreeBlocks += gpRedCoreVol->ulAlmostFreeBlocks;
|
||||
gpRedCoreVol->ulAlmostFreeBlocks = 0U;
|
||||
|
||||
ret = RedBufferFlush(0U, gpRedVolume->ulBlockCount);
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
gpRedMR->hdr.ulSignature = META_SIG_METAROOT;
|
||||
gpRedMR->hdr.ullSequence = gpRedVolume->ullSequence;
|
||||
|
||||
ret = RedVolSeqNumIncrement();
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
const uint8_t *pbMR = CAST_VOID_PTR_TO_CONST_UINT8_PTR(gpRedMR);
|
||||
uint32_t ulSectorCRC;
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
MetaRootEndianSwap(gpRedMR);
|
||||
#endif
|
||||
|
||||
gpRedMR->ulSectorCRC = 0U;
|
||||
|
||||
ulSectorCRC = RedCrc32Update(0U, &pbMR[8U], gpRedVolConf->ulSectorSize - 8U);
|
||||
|
||||
if(gpRedVolConf->ulSectorSize < REDCONF_BLOCK_SIZE)
|
||||
{
|
||||
gpRedMR->hdr.ulCRC = RedCrc32Update(ulSectorCRC, &pbMR[gpRedVolConf->ulSectorSize], REDCONF_BLOCK_SIZE - gpRedVolConf->ulSectorSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
gpRedMR->hdr.ulCRC = ulSectorCRC;
|
||||
}
|
||||
|
||||
gpRedMR->ulSectorCRC = ulSectorCRC;
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
gpRedMR->hdr.ulCRC = RedRev32(gpRedMR->hdr.ulCRC);
|
||||
gpRedMR->ulSectorCRC = RedRev32(gpRedMR->ulSectorCRC);
|
||||
#endif
|
||||
|
||||
/* Flush the block device before writing the metaroot, so that all
|
||||
previously written blocks are guaranteed to be on the media before
|
||||
the metaroot is written. Otherwise, if the block device reorders
|
||||
the writes, the metaroot could reach the media before metadata it
|
||||
points at, creating a window for disk corruption if power is lost.
|
||||
*/
|
||||
ret = RedIoFlush(gbRedVolNum);
|
||||
}
|
||||
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedIoWrite(gbRedVolNum, BLOCK_NUM_FIRST_METAROOT + gpRedCoreVol->bCurMR, 1U, gpRedMR);
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
MetaRootEndianSwap(gpRedMR);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Flush the block device to force the metaroot write to the media. This
|
||||
guarantees the transaction point is really complete before we return.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
ret = RedIoFlush(gbRedVolNum);
|
||||
}
|
||||
|
||||
/* Toggle to the other metaroot buffer. The working state and committed
|
||||
state metaroot buffers exchange places.
|
||||
*/
|
||||
if(ret == 0)
|
||||
{
|
||||
uint8_t bNextMR = 1U - gpRedCoreVol->bCurMR;
|
||||
|
||||
gpRedCoreVol->aMR[bNextMR] = *gpRedMR;
|
||||
gpRedCoreVol->bCurMR = bNextMR;
|
||||
|
||||
gpRedMR = &gpRedCoreVol->aMR[gpRedCoreVol->bCurMR];
|
||||
|
||||
gpRedCoreVol->fBranched = false;
|
||||
}
|
||||
|
||||
CRITICAL_ASSERT(ret == 0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef REDCONF_ENDIAN_SWAP
|
||||
static void MetaRootEndianSwap(
|
||||
METAROOT *pMetaRoot)
|
||||
{
|
||||
if(pMetaRoot == NULL)
|
||||
{
|
||||
REDERROR();
|
||||
}
|
||||
else
|
||||
{
|
||||
pMetaRoot->ulSectorCRC = RedRev32(pMetaRoot->ulSectorCRC);
|
||||
pMetaRoot->ulFreeBlocks = RedRev32(pMetaRoot->ulFreeBlocks);
|
||||
#if REDCONF_API_POSIX == 1
|
||||
pMetaRoot->ulFreeInodes = RedRev32(pMetaRoot->ulFreeInodes);
|
||||
#endif
|
||||
pMetaRoot->ulAllocNextBlock = RedRev32(pMetaRoot->ulAllocNextBlock);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/** @brief Process a critical file system error.
|
||||
|
||||
@param pszFileName The file in which the error occurred.
|
||||
@param ulLineNum The line number at which the error occurred.
|
||||
*/
|
||||
void RedVolCriticalError(
|
||||
const char *pszFileName,
|
||||
uint32_t ulLineNum)
|
||||
{
|
||||
#if REDCONF_OUTPUT == 1
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
if(!gpRedVolume->fReadOnly)
|
||||
{
|
||||
RedOsOutputString("Critical file system error in Reliance Edge, setting volume to READONLY\n");
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
RedOsOutputString("Critical file system error in Reliance Edge (volume already READONLY)\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
#if REDCONF_READ_ONLY == 0
|
||||
gpRedVolume->fReadOnly = true;
|
||||
#endif
|
||||
|
||||
#if REDCONF_ASSERTS == 1
|
||||
RedOsAssertFail(pszFileName, ulLineNum);
|
||||
#else
|
||||
(void)pszFileName;
|
||||
(void)ulLineNum;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/** @brief Increment the sequence number.
|
||||
|
||||
@return A negated ::REDSTATUS code indicating the operation result.
|
||||
|
||||
@retval 0 Operation was successful.
|
||||
@retval -RED_EINVAL Cannot increment sequence number: maximum value reached.
|
||||
This should not ever happen.
|
||||
*/
|
||||
REDSTATUS RedVolSeqNumIncrement(void)
|
||||
{
|
||||
REDSTATUS ret;
|
||||
|
||||
if(gpRedVolume->ullSequence == UINT64_MAX)
|
||||
{
|
||||
/* In practice this should never, ever happen; to get here, there would
|
||||
need to be UINT64_MAX disk writes, which would take eons: longer
|
||||
than the lifetime of any product or storage media. If this assert
|
||||
fires and the current year is still written with four digits,
|
||||
suspect memory corruption.
|
||||
*/
|
||||
CRITICAL_ERROR();
|
||||
ret = -RED_EFUBAR;
|
||||
}
|
||||
else
|
||||
{
|
||||
gpRedVolume->ullSequence++;
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
Reference in New Issue
Block a user