/* ----> DO NOT REMOVE THE FOLLOWING NOTICE <----
Copyright (c) 2014-2025 Tuxera US 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, see .
*/
/* Businesses and individuals that for commercial or other reasons cannot
comply with the terms of the GPLv2 license must obtain a commercial
license before incorporating Reliance Edge into proprietary software
for distribution in any form.
Visit https://www.tuxera.com/products/tuxera-edge-fs/ for more information.
*/
/** @file
@brief Implements core volume operations.
*/
#include
#include
#include
/* Minimum number of blocks needed for metadata on any volume: the master
block (1), the two metaroots (2), and one doubly-allocated inode (2),
resulting in 1 + 2 + 2 = 5.
*/
#define MINIMUM_METADATA_BLOCKS (5U)
static REDSTATUS RedVolMountMaster(uint32_t ulFlags);
static REDSTATUS RedVolMountMetaroot(uint32_t ulFlags);
static bool MetarootIsValid(METAROOT *pMR, bool *pfSectorCRCIsValid);
#if DELETE_SUPPORTED && (REDCONF_DELETE_OPEN == 1)
static REDSTATUS ConcatOrphanLists(void);
#endif
#ifdef REDCONF_ENDIAN_SWAP
static void MetaRootEndianSwap(METAROOT *pMetaRoot);
#endif
#if REDCONF_OUTPUT == 1
static void OutputCriticalError(const char *pszFileName, uint32_t ulLineNum);
static uint32_t U32toStr(char *pcBuffer, uint32_t ulBufferLen, uint32_t ulNum);
#endif
/** @brief Populate and validate the volume geometry.
The sector size and/or count will be queried from the block device if
the volume configuration specifies that one or both are to be detected
automatically. Otherwise, the values in the volume configuration are used.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL Volume geometry is invalid.
*/
REDSTATUS RedVolInitBlockGeometry(void)
{
REDSTATUS ret = 0;
const BDEVINFO *pBdevInfo = &gaRedBdevInfo[gbRedVolNum];
if( (pBdevInfo->ulSectorSize < SECTOR_SIZE_MIN)
|| ((REDCONF_BLOCK_SIZE % pBdevInfo->ulSectorSize) != 0U)
|| ((UINT64_MAX - gpRedVolConf->ullSectorOffset) < pBdevInfo->ullSectorCount)) /* SectorOffset + SectorCount must not wrap */
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
gpRedVolume->bBlockSectorShift = 0U;
while((pBdevInfo->ulSectorSize << gpRedVolume->bBlockSectorShift) < REDCONF_BLOCK_SIZE)
{
gpRedVolume->bBlockSectorShift++;
}
/* This should always be true since the block size is confirmed to
be a power of two (checked at compile time) and above we ensured
that (REDCONF_BLOCK_SIZE % pVolConf->ulSectorSize) == 0.
*/
REDASSERT((pBdevInfo->ulSectorSize << gpRedVolume->bBlockSectorShift) == REDCONF_BLOCK_SIZE);
/* Use the device block count initially, until the true volume block
count is retrieved from the master block.
*/
gpRedVolume->ulBlockCount = (uint32_t)(pBdevInfo->ullSectorCount >> gpRedVolume->bBlockSectorShift);
}
return ret;
}
/** @brief Populate the volume layout derived from the block and inode counts.
`gpRedVolume->ulBlockCount` and `gpRedCoreVol->ulInodeCount` must be
initialized by the caller before invoking this function.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL Volume geometry is invalid.
*/
REDSTATUS RedVolInitBlockLayout(void)
{
REDSTATUS ret = 0;
if(gpRedVolume->ulBlockCount < MINIMUM_METADATA_BLOCKS)
{
ret = -RED_EINVAL;
}
else
{
/* To understand the following code, note that the fixed-location
metadata is located at the start of the disk, in the following
order:
- Master block (1 block)
- Metaroots (2 blocks)
- External imap blocks (variable * 2 blocks)
- Inode blocks (pVolConf->ulInodeCount * 2 blocks)
*/
/* The imap needs bits for all inode and allocable blocks. If that
bitmap will fit into the metaroot, the inline imap is used and there
are no imap nodes on disk. The minus 3 is there since the imap does
not include bits for the master block or metaroots.
*/
gpRedCoreVol->fImapInline = (gpRedVolume->ulBlockCount - 3U) <= METAROOT_ENTRIES;
if(gpRedCoreVol->fImapInline)
{
#if REDCONF_IMAP_INLINE == 1
gpRedCoreVol->ulInodeTableStartBN = 3U;
#else
REDERROR();
ret = -RED_EINVAL;
#endif
}
else
{
#if REDCONF_IMAP_EXTERNAL == 1
gpRedCoreVol->ulImapStartBN = 3U;
/* The imap does not include bits for itself, so add two to the
number of imap entries for the two blocks of each imap node.
This allows us to divide up the remaining space, making sure to
round up so all data blocks are covered.
*/
gpRedCoreVol->ulImapNodeCount =
((gpRedVolume->ulBlockCount - 3U) + ((IMAPNODE_ENTRIES + 2U) - 1U)) / (IMAPNODE_ENTRIES + 2U);
if(gpRedCoreVol->ulImapNodeCount > METAROOT_ENTRIES)
{
/* The imap node count is too large for the metaroot bitmap.
This can happen when the volume size exceeds the maximum
supported volume size for the current block size.
*/
ret = -RED_EINVAL;
}
else
{
gpRedCoreVol->ulInodeTableStartBN = gpRedCoreVol->ulImapStartBN + (gpRedCoreVol->ulImapNodeCount * 2U);
}
#else
REDERROR();
ret = -RED_EINVAL;
#endif
}
}
/* Check for overflow.
*/
if((ret == 0) && ((((uint64_t)gpRedCoreVol->ulInodeCount * 2U) + gpRedCoreVol->ulInodeTableStartBN) > UINT32_MAX))
{
ret = -RED_EINVAL;
}
if(ret == 0)
{
gpRedCoreVol->ulFirstAllocableBN = gpRedCoreVol->ulInodeTableStartBN + (gpRedCoreVol->ulInodeCount * 2U);
if(gpRedCoreVol->ulFirstAllocableBN > gpRedVolume->ulBlockCount)
{
/* We can get here if there is not enough space for the number of
configured inodes.
*/
ret = -RED_EINVAL;
}
else
{
gpRedVolume->ulBlocksAllocable = gpRedVolume->ulBlockCount - gpRedCoreVol->ulFirstAllocableBN;
}
}
return ret;
}
/** @brief Mount a file system volume.
@param ulFlags A bitwise-OR'd mask of mount flags.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL @p ulFlags includes invalid mount flags.
@retval -RED_EIO Volume not formatted, improperly formatted, or corrupt.
*/
REDSTATUS RedVolMount(
uint32_t ulFlags)
{
REDSTATUS ret;
if(ulFlags != (ulFlags & RED_MOUNT_MASK))
{
ret = -RED_EINVAL;
}
else
{
BDEVOPENMODE mode = BDEV_O_RDONLY;
#if REDCONF_READ_ONLY == 0
if((ulFlags & RED_MOUNT_READONLY) == 0U)
{
mode = BDEV_O_RDWR;
}
#endif
ret = RedBDevOpen(gbRedVolNum, mode);
if(ret == 0)
{
ret = RedVolInitBlockGeometry();
if(ret == 0)
{
ret = RedVolMountMaster(ulFlags);
}
if(ret == 0)
{
ret = RedVolMountMetaroot(ulFlags);
}
#if DELETE_SUPPORTED && (REDCONF_DELETE_OPEN == 1)
if(ret == 0)
{
if((ulFlags & RED_MOUNT_SKIP_DELETE) == 0U)
{
ret = RedVolFreeOrphans(UINT32_MAX);
if(ret == 0)
{
/* At mount time, all orphans are defunct and should be
freed.
*/
gpRedMR->ulDefunctOrphanHead = gpRedMR->ulOrphanHead;
gpRedMR->ulOrphanHead = INODE_INVALID;
gpRedMR->ulOrphanTail = INODE_INVALID;
ret = RedVolFreeOrphans(UINT32_MAX);
}
}
else if(gpRedMR->ulDefunctOrphanHead == INODE_INVALID)
{
gpRedMR->ulDefunctOrphanHead = gpRedMR->ulOrphanHead;
gpRedMR->ulOrphanHead = INODE_INVALID;
gpRedMR->ulOrphanTail = INODE_INVALID;
}
else if(gpRedMR->ulOrphanHead != INODE_INVALID)
{
/* There are two lists, neither of which are empty, that
both contain inodes which were orphaned prior to mount.
However, the caller requested that we not free the
orphans during mount. Combine the two lists into the
defunct list, so that new orphans have a home.
*/
ret = ConcatOrphanLists();
}
else
{
/* There are orphans in the defunct list only, and the
caller has asked us not to free orphans at this time, so
there's nothing to do.
*/
}
REDASSERT( (gpRedMR->ulOrphanHead == INODE_INVALID)
== (gpRedMR->ulOrphanTail == INODE_INVALID));
}
#endif
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)RedBDevClose(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.
*/
static REDSTATUS RedVolMountMaster(
uint32_t ulFlags)
{
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, (void **)&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( !RED_DISK_LAYOUT_IS_SUPPORTED(pMB->ulVersion)
|| (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))
|| (((pMB->bFlags & MBFLAG_INODE_UIDGID) != 0U) != ((REDCONF_API_POSIX == 1) && (REDCONF_POSIX_OWNER_PERM == 1)))
|| (((pMB->bFlags & MBFLAG_DELETE_OPEN) != 0U) != ((REDCONF_API_POSIX == 1) && (REDCONF_DELETE_OPEN == 1)))
|| ((pMB->uFeaturesIncompat & MBFEATURE_MASK_INCOMPAT) != 0U)
|| ( (pMB->ulVersion >= RED_DISK_LAYOUT_POSIXIER)
&& (gaRedBdevInfo[gbRedVolNum].ulSectorSize != (1U << pMB->bSectorSizeP2))))
{
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;
/* Save the on-disk layout version so we know how to interpret
the metadata.
*/
gpRedCoreVol->ulVersion = pMB->ulVersion;
/* gpRedVolume->ulBlockCount is currently the block count derived
from the block device sector count but, on a mounted volume, it
needs to be the block count of the volume. These can be
different values since we support mounting a volume which is
smaller than the block device that it resides on.
*/
gpRedVolume->ulBlockCount = pMB->ulBlockCount;
gpRedCoreVol->ulInodeCount = pMB->ulInodeCount;
/* With the correct block and inode counts, the layout of the
volume can now be computed.
*/
ret = RedVolInitBlockLayout();
#if REDCONF_READ_ONLY == 0
if(ret == 0)
{
gpRedVolume->fReadOnly = (ulFlags & RED_MOUNT_READONLY) != 0U;
/* Check for feature flags that prevent this driver from
writing.
*/
if(!gpRedVolume->fReadOnly && ((pMB->uFeaturesReadOnly & MBFEATURE_MASK_UNWRITEABLE) != 0U))
{
ret = -RED_EROFS;
}
}
#endif
}
/* Master block buffer won't be used again, so discard it.
*/
RedBufferDiscard(pMB);
}
return ret;
}
/** @brief Mount the latest metaroot.
This function also populates the volume contexts.
@param ulFlags A bitwise-OR'd mask of mount flags.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EIO Both metaroots are missing or corrupt.
*/
static REDSTATUS RedVolMountMetaroot(
uint32_t ulFlags)
{
REDSTATUS retMR0;
REDSTATUS retMR1;
REDSTATUS ret;
retMR0 = RedIoRead(gbRedVolNum, BLOCK_NUM_FIRST_METAROOT, 1U, &gpRedCoreVol->aMR[0U]);
retMR1 = RedIoRead(gbRedVolNum, BLOCK_NUM_FIRST_METAROOT + 1U, 1U, &gpRedCoreVol->aMR[1U]);
ret = ((retMR0 == 0) || (retMR1 == 0)) ? 0 : retMR0;
/* Determine which metaroot is the most recent copy that was written
completely.
*/
if(ret == 0)
{
uint8_t bMR = UINT8_MAX;
bool fSectorCRCIsValid;
if(retMR0 == 0)
{
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) && (retMR1 == 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(gbRedVolNum);
}
if(ret == 0)
{
gpRedVolume->fMounted = true;
#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 = (const uint8_t *)pMR;
uint32_t ulSectorSize = gaRedBdevInfo[gbRedVolNum].ulSectorSize;
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], ulSectorSize - 8U);
fRet = ulCRC == ulSectorCRC;
*pfSectorCRCIsValid = fRet;
if(fRet)
{
ulCRC = RedCrc32Update(ulCRC, &pbMR[ulSectorSize], REDCONF_BLOCK_SIZE - 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 = RedBufferFlushRange(0U, gpRedVolume->ulBlockCount);
if(ret == 0)
{
gpRedMR->hdr.ulSignature = META_SIG_METAROOT;
gpRedMR->hdr.ullSequence = gpRedVolume->ullSequence;
ret = RedVolSeqNumIncrement(gbRedVolNum);
}
if(ret == 0)
{
const uint8_t *pbMR = (const uint8_t *)gpRedMR;
uint32_t ulSectorSize = gaRedBdevInfo[gbRedVolNum].ulSectorSize;
uint32_t ulSectorCRC;
#ifdef REDCONF_ENDIAN_SWAP
MetaRootEndianSwap(gpRedMR);
#endif
gpRedMR->ulSectorCRC = 0U;
ulSectorCRC = RedCrc32Update(0U, &pbMR[8U], ulSectorSize - 8U);
if(ulSectorSize < REDCONF_BLOCK_SIZE)
{
gpRedMR->hdr.ulCRC = RedCrc32Update(ulSectorCRC, &pbMR[ulSectorSize], REDCONF_BLOCK_SIZE - 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;
}
/** @brief Rollback to the previous transaction point.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EIO An I/O error occurred.
*/
REDSTATUS RedVolRollback(void)
{
REDSTATUS ret = 0;
REDASSERT(gpRedVolume->fMounted); /* Should be checked by caller. */
REDASSERT(!gpRedVolume->fReadOnly); /* Should be checked by caller. */
if(gpRedCoreVol->fBranched)
{
uint32_t ulFlags = RED_MOUNT_DEFAULT;
ret = RedBufferDiscardRange(0U, gpRedVolume->ulBlockCount);
if(ret == 0)
{
ret = RedVolMountMaster(ulFlags);
}
if(ret == 0)
{
ret = RedVolMountMetaroot(ulFlags);
}
if(ret == 0)
{
gpRedCoreVol->fBranched = false;
}
CRITICAL_ASSERT(ret == 0);
}
return ret;
}
#endif /* REDCONF_READ_ONLY == 0 */
/** @brief Yields the number of currently available free blocks.
Accounts for reserved blocks, subtracting the number of reserved blocks if
they are unavailable.
@return Number of currently available free blocks.
*/
uint32_t RedVolFreeBlockCount(void)
{
uint32_t ulFreeBlocks = gpRedMR->ulFreeBlocks;
#if RESERVED_BLOCKS > 0U
if(!gpRedCoreVol->fUseReservedBlocks)
{
if(ulFreeBlocks >= RESERVED_BLOCKS)
{
ulFreeBlocks -= RESERVED_BLOCKS;
}
else
{
ulFreeBlocks = 0U;
}
}
#endif
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_FRESERVE == 1)
if(!gpRedCoreVol->fUseReservedInodeBlocks)
{
if(ulFreeBlocks < gpRedCoreVol->ulReservedInodeBlocks)
{
REDERROR();
ulFreeBlocks = 0U;
}
else
{
ulFreeBlocks -= gpRedCoreVol->ulReservedInodeBlocks;
}
if(gpRedCoreVol->ulReservedInodes > 0U)
{
uint32_t ulBranchBlocks = gpRedCoreVol->ulReservedInodes * INODE_MAX_DEPTH;
/* The blocks set aside for freserve branching are, for simplicity,
always reserved: even if they have already been branched. If blocks
are both reserved and branched, they are double-counted against free
space, and so it's possible for this reserved count to be larger
than remaining free space.
*/
ulFreeBlocks -= REDMIN(ulFreeBlocks, ulBranchBlocks);
}
}
#endif
return ulFreeBlocks;
}
#if DELETE_SUPPORTED && (REDCONF_DELETE_OPEN == 1)
/** @brief Free inodes which were orphaned before the most recent mount of the
volume (defunct orphans).
If there are fewer defunct orphans than were requested, all defunct orphans
will be freed.
@param ulCount The maximum number of defunct orphans to free.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL @p ulCount is zero.
@retval -RED_EIO A disk I/O error occurred.
*/
REDSTATUS RedVolFreeOrphans(
uint32_t ulCount)
{
REDSTATUS ret = 0;
if(ulCount == 0U)
{
REDERROR();
ret = -RED_EINVAL;
}
else
{
uint32_t ulIdx;
/* Inode numbers are 32-bits, thus a count of UINT32_MAX will always be
sufficient to include all defunct orphans.
*/
for(ulIdx = 0U; ulIdx < ulCount; ulIdx++)
{
CINODE ino;
ino.ulInode = gpRedMR->ulDefunctOrphanHead;
ret = RedInodeMount(&ino, FTYPE_ANY, false);
if(ret == 0)
{
uint32_t ulNextInode = ino.pInodeBuf->ulNextOrphan;
ret = RedInodeFreeOrphan(&ino);
if(ret == 0)
{
gpRedMR->ulDefunctOrphanHead = ulNextInode;
}
}
if(ret != 0)
{
break;
}
}
/* RED_EBADF is the only expected error, which can be returned by
RedInodeMount() when we reach the end of the list. However,
RedInodeMount() will also return RED_EBADF for invalid inodes, which
is a critical error. Thus the special handling of RED_EBADF here.
*/
if(ret == -RED_EBADF)
{
if(gpRedMR->ulDefunctOrphanHead == INODE_INVALID)
{
/* The loop above does not look for the end of the list
(indicated by an orphan list value of INODE_INVALID). It
will instead call RedInodeMount() with the inode number
INODE_INVALID, which will return -RED_EBADF. That condition
is not an error for this function because the count is a
maximum.
*/
ret = 0;
}
else
{
/* The loop above encountered an inode in the list that is not
valid.
*/
CRITICAL_ERROR();
ret = -RED_EFUBAR;
}
}
}
return ret;
}
/** @brief Concatenate the two lists of orphans.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EIO A disk I/O error occurred.
*/
static REDSTATUS ConcatOrphanLists(void)
{
CINODE ino;
REDSTATUS ret;
REDASSERT(gpRedMR->ulDefunctOrphanHead != INODE_INVALID);
REDASSERT(gpRedMR->ulOrphanHead != INODE_INVALID);
REDASSERT(gpRedMR->ulOrphanTail != INODE_INVALID);
ino.ulInode = gpRedMR->ulOrphanTail;
ret = RedInodeMount(&ino, FTYPE_ANY, true);
if(ret == 0)
{
if(ino.pInodeBuf->ulNextOrphan != INODE_INVALID)
{
CRITICAL_ERROR();
ret = -RED_EFUBAR;
}
else
{
ino.pInodeBuf->ulNextOrphan = gpRedMR->ulDefunctOrphanHead;
gpRedMR->ulDefunctOrphanHead = gpRedMR->ulOrphanHead;
gpRedMR->ulOrphanHead = INODE_INVALID;
gpRedMR->ulOrphanTail = INODE_INVALID;
}
RedInodePut(&ino, 0U);
}
return ret;
}
#endif /* DELETE_SUPPORTED && (REDCONF_DELETE_OPEN == 1) */
#ifdef REDCONF_ENDIAN_SWAP
static void MetaRootEndianSwap(
METAROOT *pMetaRoot)
{
if(pMetaRoot == NULL)
{
REDERROR();
}
else
{
pMetaRoot->hdr.ulSignature = RedRev32(pMetaRoot->hdr.ulSignature);
pMetaRoot->hdr.ullSequence = RedRev64(pMetaRoot->hdr.ullSequence);
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);
#if (REDCONF_API_POSIX == 1) && (REDCONF_DELETE_OPEN == 1)
pMetaRoot->ulDefunctOrphanHead = RedRev32(pMetaRoot->ulDefunctOrphanHead);
pMetaRoot->ulOrphanHead = RedRev32(pMetaRoot->ulOrphanHead);
pMetaRoot->ulOrphanTail = RedRev32(pMetaRoot->ulOrphanTail);
#endif
}
}
#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)
{
/* Unused in some configurations
*/
(void)pszFileName;
(void)ulLineNum;
#if REDCONF_OUTPUT == 1
OutputCriticalError(pszFileName, ulLineNum);
#endif
#if REDCONF_READ_ONLY == 0
gpRedVolume->fReadOnly = true;
#endif
#if REDCONF_ASSERTS == 1
RedOsAssertFail(pszFileName, ulLineNum);
#endif
}
/** @brief Increment the sequence number.
@param bVolNum Volume number of the volume whose sequence number is to be
incremented.
@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(
uint8_t bVolNum)
{
REDSTATUS ret;
if(bVolNum >= REDCONF_VOLUME_COUNT)
{
REDERROR();
ret = -RED_EINVAL;
}
else if(gaRedVolume[bVolNum].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
{
gaRedVolume[bVolNum].ullSequence++;
ret = 0;
}
return ret;
}
#if REDCONF_OUTPUT == 1
/** @brief Output a critical error message.
@param pszFileName The file in which the error occurred.
@param ulLineNum The line number at which the error occurred.
*/
static void OutputCriticalError(
const char *pszFileName,
uint32_t ulLineNum)
{
#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");
}
/* Print @p pszFileName and @p ulLineNum.
*/
if(pszFileName == NULL)
{
REDERROR();
}
else
{
#define FILENAME_MAX_LEN 24U /* 2x the longest core source file name */
#define LINENUM_MAX_LEN 10U /* Big enough for UINT32_MAX */
#define PREFIX_LEN (sizeof(szPrefix) - 1U)
#define OUTBUFSIZE (PREFIX_LEN + FILENAME_MAX_LEN + 1U /* ':' */ + LINENUM_MAX_LEN + 2U /* "\n\0" */)
const char szPrefix[] = "Reliance Edge critical error at ";
char szBuffer[OUTBUFSIZE];
const char *pszBaseName;
uint32_t ulNameLen;
uint32_t ulIdx;
/* Many compilers include the path in __FILE__ strings. szBuffer
isn't large enough to print paths, so find the basename.
If __FILE_NAME__ is defined (as is the case for recent GCC and
Clang), we use it instead of __FILE__. It's assumed that
__FILE_NAME__ expands to the basename and hence there's no need
to skip over directory path components.
*/
pszBaseName = pszFileName;
ulIdx = 0U;
#ifndef __FILE_NAME__
while(pszFileName[ulIdx] != '\0')
{
/* Currently it's safe to assume that the host system uses slashes
as path separators. On Unix-like hosts, a backslash is also a
legal file name character, but we don't need to worry about that
edge case, since only the last slash matters, and _our_ file
names will never include backslashes.
*/
if((pszFileName[ulIdx] == '/') || (pszFileName[ulIdx] == '\\'))
{
pszBaseName = &pszFileName[ulIdx + 1U];
}
ulIdx++;
}
#endif
ulNameLen = RedStrLen(pszBaseName);
ulNameLen = REDMIN(ulNameLen, FILENAME_MAX_LEN); /* Paranoia */
/* We never use printf() in the core, for the sake of portability
and minimal code size. Instead, craft a string buffer for
RedOsOutputString().
E.g., "Reliance Edge critical error at file.c:123\n"
*/
RedMemCpy(szBuffer, szPrefix, PREFIX_LEN);
ulIdx = PREFIX_LEN;
RedMemCpy(&szBuffer[ulIdx], pszBaseName, ulNameLen);
ulIdx += ulNameLen;
szBuffer[ulIdx] = ':';
ulIdx++;
ulIdx += U32toStr(&szBuffer[ulIdx], sizeof(szBuffer) - ulIdx, ulLineNum);
szBuffer[ulIdx] = '\n';
ulIdx++;
szBuffer[ulIdx] = '\0';
RedOsOutputString(szBuffer);
}
}
/** @brief Output a decimal string representation of a uint32_t value.
@note This function does _not_ null-terminate the output buffer.
@param pcBuffer The output buffer.
@param ulBufferLen The size of @p pcBuffer.
@param ulNum The unsigned 32-bit number.
@return The number of bytes written to @p pcBuffer.
*/
static uint32_t U32toStr(
char *pcBuffer,
uint32_t ulBufferLen,
uint32_t ulNum)
{
uint32_t ulBufferIdx = 0U;
if((pcBuffer == NULL) || (ulBufferLen == 0U))
{
REDERROR();
}
else
{
const char szDigits[] = "0123456789";
char ach[10U]; /* Big enough for a uint32_t in radix 10 */
uint32_t ulDigits = 0U;
uint32_t ulRemaining = ulNum;
/* Compute the digit characters, from least significant to most
significant. For example, if @p ulNum is 123, the ach array
is populated with "321".
*/
do
{
ach[ulDigits] = szDigits[ulRemaining % 10U];
ulRemaining /= 10U;
ulDigits++;
}
while(ulRemaining > 0U);
/* Copy and reverse the string. For example, if the ach array
contains "321", then "123" is written to @p pcBuffer.
*/
while((ulDigits > 0U) && (ulBufferIdx < ulBufferLen))
{
ulDigits--;
pcBuffer[ulBufferIdx] = ach[ulDigits];
ulBufferIdx++;
}
}
return ulBufferIdx;
}
#endif /* REDCONF_OUTPUT == 1 */