Initial version

This commit is contained in:
2019-06-28 23:08:36 +02:00
commit 4d8973e20b
2426 changed files with 948029 additions and 0 deletions

View File

@ -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

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

@ -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

View File

@ -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;
}