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