/* ----> 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 inode functions. */ #include #include #if REDCONF_READ_ONLY == 0 static REDSTATUS InodeIsBranched(uint32_t ulInode, bool *pfIsBranched); #endif #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) static REDSTATUS InodeFindFree(uint32_t *pulInode); #endif #if REDCONF_READ_ONLY == 0 static REDSTATUS InodeGetWriteableCopy(uint32_t ulInode, uint8_t *pbWhich); #endif static REDSTATUS InodeGetCurrentCopy(uint32_t ulInode, uint8_t *pbWhich); #if REDCONF_READ_ONLY == 0 static REDSTATUS InodeBitSet(uint32_t ulInode, uint8_t bWhich, bool fAllocated); #endif static uint32_t InodeBlock(uint32_t ulInode, uint8_t bWhich); /** @brief Mount an existing inode. Will populate all fields of the cached inode structure, except those which are populated during seek. @param pInode A pointer to the cached inode structure. The pInode->ulInode field must already be initialized with the inode number to mount. All other fields will be discarded. @param type The expected inode type. @param fBranch Whether to branch the inode. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL Invalid parameters. @retval -RED_EROFS @p fBranch is true but the driver is read-only. @retval -RED_EIO A disk I/O error occurred. @retval -RED_EBADF The inode number is free; or the inode number is not valid. @retval -RED_EISDIR @p type is ::FTYPE_FILE and the inode is a directory. @retval -RED_ENOTDIR @p type is ::FTYPE_DIR and the inode is a file. */ REDSTATUS RedInodeMount( CINODE *pInode, FTYPE type, bool fBranch) { REDSTATUS ret = 0; if(pInode == NULL) { ret = -RED_EINVAL; } else if(!INODE_IS_VALID(pInode->ulInode)) { ret = -RED_EBADF; } #if REDCONF_API_FSE == 1 else if(type == FTYPE_DIR) { REDERROR(); ret = -RED_EINVAL; } #endif #if REDCONF_READ_ONLY == 1 else if(fBranch) { REDERROR(); ret = -RED_EROFS; } #endif else { uint32_t ulInode = pInode->ulInode; uint8_t bWhich = 0U; /* Init'd to quiet warnings. */ RedMemSet(pInode, 0U, sizeof(*pInode)); pInode->ulInode = ulInode; ret = InodeGetCurrentCopy(pInode->ulInode, &bWhich); if(ret == 0) { ret = RedBufferGet(InodeBlock(pInode->ulInode, bWhich), BFLAG_META_INODE, CAST_VOID_PTR_PTR(&pInode->pInodeBuf)); } #if REDCONF_READ_ONLY == 0 if(ret == 0) { ret = InodeIsBranched(pInode->ulInode, &pInode->fBranched); } #endif if(ret == 0) { if(RED_S_ISREG(pInode->pInodeBuf->uMode)) { #if REDCONF_API_POSIX == 1 pInode->fDirectory = false; if(type == FTYPE_DIR) { ret = -RED_ENOTDIR; } #endif } #if REDCONF_API_POSIX == 1 else if(RED_S_ISDIR(pInode->pInodeBuf->uMode)) { pInode->fDirectory = true; if(type == FTYPE_FILE) { ret = -RED_EISDIR; } } #endif else { /* Missing or unsupported inode type. */ CRITICAL_ERROR(); ret = -RED_EFUBAR; } } #if REDCONF_READ_ONLY == 0 if((ret == 0) && fBranch) { ret = RedInodeBranch(pInode); } #endif if(ret != 0) { RedInodePut(pInode, 0U); } } return ret; } #if (REDCONF_READ_ONLY == 0) && ((REDCONF_API_POSIX == 1) || FORMAT_SUPPORTED) /** @brief Create an inode. @param pInode Pointer to the cached inode structure. If pInode->ulInode is #INODE_INVALID, a free inode will be found; otherwise, pInode->ulInode will be the inode number (an error will be returned if it is not free). @param ulPInode The parent inode number. @param uMode The inode mode. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EBADF pInode->ulInode is an invalid inode number other than #INODE_INVALID. @retval -RED_EINVAL Invalid parameters. @retval -RED_EEXIST Tried to create an inode with an inode number that is already in use. @retval -RED_ENFILE All inode slots are already in use. @retval -RED_EIO A disk I/O error occurred. */ REDSTATUS RedInodeCreate( CINODE *pInode, uint32_t ulPInode, uint16_t uMode) { REDSTATUS ret; #if REDCONF_API_POSIX == 1 /* ulPInode must be a valid inode number, unless we are creating the root directory, in which case ulPInode must be INODE_INVALID (the root directory has no parent). */ if( (pInode == NULL) || (!INODE_IS_VALID(ulPInode) && ((ulPInode != INODE_INVALID) || (pInode->ulInode != INODE_ROOTDIR)))) #else if(pInode == NULL) #endif { REDERROR(); ret = -RED_EINVAL; } else { uint32_t ulInode = pInode->ulInode; RedMemSet(pInode, 0U, sizeof(*pInode)); #if REDCONF_API_POSIX == 1 if(ulInode == INODE_INVALID) { /* Caller requested that an inode number be allocated. Search for an unused inode number, error if there isn't one. */ ret = InodeFindFree(&pInode->ulInode); } else #endif { /* Caller requested creation of a specific inode number. Make sure it's valid and doesn't already exist. */ if(INODE_IS_VALID(ulInode)) { bool fFree; ret = RedInodeIsFree(ulInode, &fFree); if(ret == 0) { if(fFree) { pInode->ulInode = ulInode; } else { ret = -RED_EEXIST; } } } else { ret = -RED_EBADF; } } if(ret == 0) { uint8_t bWriteableWhich; ret = InodeGetWriteableCopy(pInode->ulInode, &bWriteableWhich); if(ret == 0) { ret = RedBufferGet(InodeBlock(pInode->ulInode, bWriteableWhich), (uint16_t)((uint32_t)BFLAG_META_INODE | BFLAG_DIRTY | BFLAG_NEW), CAST_VOID_PTR_PTR(&pInode->pInodeBuf)); if(ret == 0) { /* Mark the inode block as allocated. */ ret = InodeBitSet(pInode->ulInode, bWriteableWhich, true); if(ret != 0) { RedBufferPut(pInode->pInodeBuf); } } } } if(ret == 0) { #if REDCONF_INODE_TIMESTAMPS == 1 uint32_t ulNow = RedOsClockGetTime(); pInode->pInodeBuf->ulATime = ulNow; pInode->pInodeBuf->ulMTime = ulNow; pInode->pInodeBuf->ulCTime = ulNow; #endif pInode->pInodeBuf->uMode = uMode; #if REDCONF_API_POSIX == 1 #if REDCONF_API_POSIX_LINK == 1 pInode->pInodeBuf->uNLink = 1U; #endif pInode->pInodeBuf->ulPInode = ulPInode; #else (void)ulPInode; #endif pInode->fBranched = true; pInode->fDirty = true; #if REDCONF_API_POSIX == 1 gpRedMR->ulFreeInodes--; #endif } } return ret; } #endif /* (REDCONF_READ_ONLY == 0) && ((REDCONF_API_POSIX == 1) || FORMAT_SUPPORTED) */ #if DELETE_SUPPORTED /** @brief Delete an inode. @param pInode Pointer to the cached inode structure. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EBADF The inode is free. @retval -RED_EINVAL @p pInode is `NULL`; or pInode->pBuffer is `NULL`. @retval -RED_EIO A disk I/O error occurred. */ REDSTATUS RedInodeDelete( CINODE *pInode) { REDSTATUS ret = 0; if(!CINODE_IS_MOUNTED(pInode)) { ret = -RED_EINVAL; } else { if(pInode->pInodeBuf->ullSize != 0U) { ret = RedInodeDataTruncate(pInode, UINT64_SUFFIX(0)); } if(ret == 0) { ret = RedInodeFree(pInode); } } return ret; } /** @brief Decrement an inode link count and delete the inode if the link count falls to zero. @param pInode A pointer to the cached inode structure. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p pInode is not a mounted cachde inode. @retval -RED_EIO A disk I/O error occurred. */ REDSTATUS RedInodeLinkDec( CINODE *pInode) { REDSTATUS ret; if(!CINODE_IS_MOUNTED(pInode)) { ret = -RED_EINVAL; } #if REDCONF_API_POSIX_LINK == 1 else if(pInode->pInodeBuf->uNLink > 1U) { ret = RedInodeBranch(pInode); if(ret == 0) { pInode->pInodeBuf->uNLink--; } } #endif else { ret = RedInodeDelete(pInode); } return ret; } #endif /* DELETE_SUPPORTED */ #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) /** @brief Free an inode. @param pInode Pointer to the cached inode structure. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EBADF The inode is free. @retval -RED_EINVAL @p pInode is `NULL`; or pInode->pBuffer is `NULL`. @retval -RED_EIO A disk I/O error occurred. */ REDSTATUS RedInodeFree( CINODE *pInode) { REDSTATUS ret; if(!CINODE_IS_MOUNTED(pInode)) { ret = -RED_EINVAL; } else { bool fSlot0Allocated; RedBufferDiscard(pInode->pInodeBuf); pInode->pInodeBuf = NULL; /* Determine which of the two slots for the inode is currently allocated, and free that slot. */ ret = RedInodeBitGet(gpRedCoreVol->bCurMR, pInode->ulInode, 0U, &fSlot0Allocated); if(ret == 0) { bool fSlot1Allocated; ret = RedInodeBitGet(gpRedCoreVol->bCurMR, pInode->ulInode, 1U, &fSlot1Allocated); if(ret == 0) { if(fSlot0Allocated) { if(fSlot1Allocated) { /* Both inode slots should never be allocated at the same time. */ CRITICAL_ERROR(); ret = -RED_EFUBAR; } else { ret = InodeBitSet(pInode->ulInode, 0U, false); } } else { if(!fSlot1Allocated) { /* The inode in unallocated, which should have been caught when it was mounted. */ CRITICAL_ERROR(); ret = -RED_EBADF; } else { ret = InodeBitSet(pInode->ulInode, 1U, false); } } } } pInode->ulInode = INODE_INVALID; if(ret == 0) { if(gpRedMR->ulFreeInodes >= gpRedVolConf->ulInodeCount) { CRITICAL_ERROR(); ret = -RED_EFUBAR; } else { gpRedMR->ulFreeInodes++; } } } return ret; } #endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) */ /** @brief Put the cached inode structure. This puts all of the buffers in the ::CINODE structure. Also updates inode timestamp fields if requested. @param pInode The cached inode structure. @param bTimeFields The inode timestamp fields to update. */ void RedInodePut( CINODE *pInode, uint8_t bTimeFields) { if(pInode == NULL) { REDERROR(); } else { RedInodePutCoord(pInode); if(pInode->pInodeBuf != NULL) { #if (REDCONF_READ_ONLY == 0) && (REDCONF_INODE_TIMESTAMPS == 1) if((bTimeFields & IPUT_UPDATE_MASK) != 0U) { if(!pInode->fBranched || !pInode->fDirty) { REDERROR(); } else { uint32_t ulNow = RedOsClockGetTime(); #if REDCONF_ATIME == 1 if((bTimeFields & IPUT_UPDATE_ATIME) != 0U) { pInode->pInodeBuf->ulATime = ulNow; } #endif if((bTimeFields & IPUT_UPDATE_MTIME) != 0U) { pInode->pInodeBuf->ulMTime = ulNow; } if((bTimeFields & IPUT_UPDATE_CTIME) != 0U) { pInode->pInodeBuf->ulCTime = ulNow; } } } #else (void)bTimeFields; #endif RedBufferPut(pInode->pInodeBuf); pInode->pInodeBuf = NULL; } } } /** @brief Put all buffers in the cached inode structure except for the inode node buffer. @param pInode A pointer to the cached inode structure. */ void RedInodePutCoord( CINODE *pInode) { if(pInode == NULL) { REDERROR(); } else { RedInodePutData(pInode); #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES RedInodePutIndir(pInode); #endif #if DINDIR_POINTERS > 0U RedInodePutDindir(pInode); #endif } } #if DINDIR_POINTERS > 0U /** @brief Put the double indirect buffer. @param pInode A pointer to the cached inode structure. */ void RedInodePutDindir( CINODE *pInode) { if(pInode == NULL) { REDERROR(); } else if(pInode->pDindir != NULL) { RedBufferPut(pInode->pDindir); pInode->pDindir = NULL; } else { /* No double indirect buffer, nothing to put. */ } } #endif #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES /** @brief Put the indirect buffer. @param pInode A pointer to the cached inode structure. */ void RedInodePutIndir( CINODE *pInode) { if(pInode == NULL) { REDERROR(); } else if(pInode->pIndir != NULL) { RedBufferPut(pInode->pIndir); pInode->pIndir = NULL; } else { /* No indirect buffer, nothing to put. */ } } #endif /** @brief Put the inode data buffer. @param pInode A pointer to the cached inode structure. */ void RedInodePutData( CINODE *pInode) { if(pInode == NULL) { REDERROR(); } else if(pInode->pbData != NULL) { RedBufferPut(pInode->pbData); pInode->pbData = NULL; } else { /* No data buffer, nothing to put. */ } } #if REDCONF_READ_ONLY == 0 /** @brief Determine if an inode is branched. @param ulInode The inode number to examine. @param pfIsBranched On successful return, populated with whether the inode is branched. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p pInode is `NULL`; or @p ulInode is not a valid inode number. @retval -RED_EIO A disk I/O error occurred. */ static REDSTATUS InodeIsBranched( uint32_t ulInode, bool *pfIsBranched) { REDSTATUS ret; if(!INODE_IS_VALID(ulInode) || (pfIsBranched == NULL)) { REDERROR(); ret = -RED_EINVAL; } else { ALLOCSTATE state; ret = RedImapBlockState(InodeBlock(ulInode, 0U), &state); if(ret == 0) { if(state == ALLOCSTATE_NEW) { *pfIsBranched = true; } else { ret = RedImapBlockState(InodeBlock(ulInode, 1U), &state); if(ret == 0) { if(state == ALLOCSTATE_NEW) { *pfIsBranched = true; } else { *pfIsBranched = false; } } } } } return ret; } /** @brief Branch an inode. A branched inode is one in which the allocation state for one copy is free or almost free, and the other copy is in the new state. The copy which is in the new state is the writeable copy, which is also buffered and dirty. @param pInode Pointer to the cached inode structure which has already been mounted. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL Invalid parameters. @retval -RED_EIO A disk I/O error occurred. */ REDSTATUS RedInodeBranch( CINODE *pInode) { REDSTATUS ret; if(!CINODE_IS_MOUNTED(pInode)) { REDERROR(); ret = -RED_EINVAL; } else if(!pInode->fBranched) { uint8_t bWhich; ret = InodeGetWriteableCopy(pInode->ulInode, &bWhich); if(ret == 0) { RedBufferBranch(pInode->pInodeBuf, InodeBlock(pInode->ulInode, bWhich)); pInode->fBranched = true; pInode->fDirty = true; } /* Toggle the inode slots: the old slot block becomes almost free (still used by the committed state) and the new slot block becomes new. */ if(ret == 0) { ret = InodeBitSet(pInode->ulInode, 1U - bWhich, false); } if(ret == 0) { ret = InodeBitSet(pInode->ulInode, bWhich, true); } CRITICAL_ASSERT(ret == 0); } else { RedBufferDirty(pInode->pInodeBuf); pInode->fDirty = true; ret = 0; } return ret; } #endif /* REDCONF_READ_ONLY == 0 */ #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) /** @brief Find a free inode number. @param pulInode On successful return, populated with a free inode number. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p pulInode is `NULL`. @retval -RED_EIO A disk I/O error occurred. @retval -RED_ENFILE No available inode numbers. */ static REDSTATUS InodeFindFree( uint32_t *pulInode) { REDSTATUS ret; if(pulInode == NULL) { REDERROR(); ret = -RED_EINVAL; } else if(gpRedMR->ulFreeInodes == 0U) { ret = -RED_ENFILE; } else { uint32_t ulInode; ret = 0; for(ulInode = INODE_FIRST_FREE; ulInode < (INODE_FIRST_VALID + gpRedVolConf->ulInodeCount); ulInode++) { bool fFree; ret = RedInodeIsFree(ulInode, &fFree); if((ret != 0) || fFree) { break; } } if(ret == 0) { if(ulInode < (INODE_FIRST_VALID + gpRedVolConf->ulInodeCount)) { *pulInode = ulInode; } else { /* If gpRedMR->ulFreeInodes > 0, we should have found an inode. */ CRITICAL_ERROR(); ret = -RED_ENFILE; } } } return ret; } #endif #if ((REDCONF_READ_ONLY == 0) && ((REDCONF_API_POSIX == 1) || FORMAT_SUPPORTED)) || (REDCONF_CHECKER == 1) /** @brief Determine whether an inode number is available. @param ulInode The node number to examine. @param pfFree On successful return, populated with whether the inode number is available (true) or in use (false). @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p pfFree is `NULL`; or @p ulInode is not a valid inode number. @retval -RED_EIO A disk I/O error occurred. */ REDSTATUS RedInodeIsFree( uint32_t ulInode, bool *pfFree) { REDSTATUS ret; if(pfFree == NULL) { REDERROR(); ret = -RED_EINVAL; } else { bool fSlot0Allocated; *pfFree = false; ret = RedInodeBitGet(gpRedCoreVol->bCurMR, ulInode, 0U, &fSlot0Allocated); if((ret == 0) && !fSlot0Allocated) { bool fSlot1Allocated; ret = RedInodeBitGet(gpRedCoreVol->bCurMR, ulInode, 1U, &fSlot1Allocated); if((ret == 0) && !fSlot1Allocated) { *pfFree = true; } } } return ret; } #endif #if REDCONF_READ_ONLY == 0 /** @brief Determine which copy of the inode is currently writeable. @param ulInode The inode number to examine. @param pbWhich On successful return, populated with which copy of the inode (either 0 or 1) is writeable. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p pbWhich is `NULL`; or ulInode is not a valid inode number. @retval -RED_EIO A disk I/O error occurred. */ static REDSTATUS InodeGetWriteableCopy( uint32_t ulInode, uint8_t *pbWhich) { REDSTATUS ret; if(pbWhich == NULL) { REDERROR(); ret = -RED_EINVAL; } else { bool fSlot0Allocated; /* The writeable inode slot is the one which is free in the committed state, so query the committed state metaroot. */ ret = RedInodeBitGet(1U - gpRedCoreVol->bCurMR, ulInode, 0U, &fSlot0Allocated); if(ret == 0) { if(!fSlot0Allocated) { *pbWhich = 0U; } else { bool fSlot1Allocated; ret = RedInodeBitGet(1U - gpRedCoreVol->bCurMR, ulInode, 1U, &fSlot1Allocated); if(ret == 0) { if(!fSlot1Allocated) { *pbWhich = 1U; } else { /* Both inode slots were allocated, which should never happen. */ CRITICAL_ERROR(); ret = -RED_EFUBAR; } } } } } return ret; } #endif /** @brief Determine which copy of the inode is current. @param ulInode The inode number to examine. @param pbWhich On successful return, populated with which copy of the inode (either 0 or 1) is current. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EBADF @p ulInode is an unallocated inode number. @retval -RED_EINVAL @p pbWhich is `NULL`; or ulInode is not a valid inode number. @retval -RED_EIO A disk I/O error occurred. */ static REDSTATUS InodeGetCurrentCopy( uint32_t ulInode, uint8_t *pbWhich) { REDSTATUS ret; if(pbWhich == NULL) { REDERROR(); ret = -RED_EINVAL; } else { bool fSlot0Allocated; /* The current inode slot is the one which is allocated in the working state metaroot. */ ret = RedInodeBitGet(gpRedCoreVol->bCurMR, ulInode, 0U, &fSlot0Allocated); if(ret == 0) { if(fSlot0Allocated) { *pbWhich = 0U; } else { bool fSlot1Allocated; ret = RedInodeBitGet(gpRedCoreVol->bCurMR, ulInode, 1U, &fSlot1Allocated); if(ret == 0) { if(fSlot1Allocated) { *pbWhich = 1U; } else { /* Neither slot for this inode was allocated, so the inode is actually free. */ ret = -RED_EBADF; } } } } } return ret; } /** @brief Get whether a copy of an inode is allocated. @param bMR The metaroot index: either 0 or 1. @param ulInode The inode number. @param bWhich Which copy of the inode to get. @param pfAllocated On successful return, populated with whether the given copy of the inode is allocated. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p bMR is not 1 or 0; @p ulInode is not a valid inode number; or @p bWhich is not 1 or 0; or @p pfAllocated is `NULL`. @retval -RED_EIO A disk I/O error occurred. */ REDSTATUS RedInodeBitGet( uint8_t bMR, uint32_t ulInode, uint8_t bWhich, bool *pfAllocated) { REDSTATUS ret; if(!INODE_IS_VALID(ulInode) || (bWhich > 1U)) { REDERROR(); ret = -RED_EINVAL; } else { ret = RedImapBlockGet(bMR, InodeBlock(ulInode, bWhich), pfAllocated); } return ret; } #if REDCONF_READ_ONLY == 0 /** @brief Set whether a copy of an inode is allocated. @param ulInode The inode number. @param bWhich Which copy of the inode to set. @param fAllocated If true, the inode is set to allocated; if false, the inode is set to free. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p ulInode is not a valid inode number; or @p bWhich is not 1 or 0. @retval -RED_EIO A disk I/O error occurred. */ static REDSTATUS InodeBitSet( uint32_t ulInode, uint8_t bWhich, bool fAllocated) { REDSTATUS ret; if(!INODE_IS_VALID(ulInode) || (bWhich > 1U)) { REDERROR(); ret = -RED_EINVAL; } else { ret = RedImapBlockSet(InodeBlock(ulInode, bWhich), fAllocated); } return ret; } #endif /** @brief Determine the block number of an inode. @param ulInode The inode number. @param bWhich Which copy of the inode. @return The block number of the inode. */ static uint32_t InodeBlock( uint32_t ulInode, uint8_t bWhich) { REDASSERT(INODE_IS_VALID(ulInode)); REDASSERT(bWhich <= 1U); return gpRedCoreVol->ulInodeTableStartBN + ((ulInode - INODE_FIRST_VALID) * 2U) + bWhich; }