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