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