/** @brief Configure a block device. @note This is a non-standard block device API! The standard block device APIs are designed for implementations running on targets with block devices that are known in advance and can be statically defined by the implementation. However, this implementation is intended for host systems, and it needs to support writing to raw disks (like "H:" etc.) and file disks which are supplied on the command line. @param bVolNum The volume number of the volume to configure. @param pszBDevSpec Drive or file to associate with the volume. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p bVolNum is not a valid volume number; or @p pszBDevSpec is `NULL` or an empty string. */ REDSTATUS RedOsBDevConfig( uint8_t bVolNum, const char *pszBDevSpec) { REDSTATUS ret; if((bVolNum >= REDCONF_VOLUME_COUNT) || gaDisk[bVolNum].fOpen || (pszBDevSpec == NULL) || (pszBDevSpec[0U] == '\0')) { ret = -RED_EINVAL; } else { RedMemSet(&gaDisk[bVolNum], 0U, sizeof(gaDisk[bVolNum])); gaDisk[bVolNum].pszSpec = pszBDevSpec; if(IsDriveSpec(pszBDevSpec)) { gaDisk[bVolNum].type = BDEVTYPE_RAW_DISK; } else { gaDisk[bVolNum].type = BDEVTYPE_FILE_DISK; } ret = 0; } return ret; }
/** @brief Initialize the buffers. */ void RedBufferInit(void) { uint8_t bIdx; RedMemSet(&gBufCtx, 0U, sizeof(gBufCtx)); for(bIdx = 0U; bIdx < REDCONF_BUFFER_COUNT; bIdx++) { /* When the buffers have been freshly initialized, acquire the buffers in the order in which they appear in the array. */ gBufCtx.abMRU[bIdx] = (uint8_t)((REDCONF_BUFFER_COUNT - bIdx) - 1U); gBufCtx.aHead[bIdx].ulBlock = BBLK_INVALID; } }
void *calloc(size_t size1, size_t size2) { size_t totalSize = size1 * size2; void *ptr; if(totalSize < size1 || totalSize < size2) { return NULL; } ptr = malloc(totalSize); if(ptr == NULL) { return NULL; } RedMemSet(ptr, 0U, totalSize); return ptr; }
/** @brief Acquire a buffer. @param ulBlock Block number to acquire. @param uFlags BFLAG_ values for the operation. @param ppBuffer On success, populated with the acquired buffer. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EIO A disk I/O error occurred. @retval -RED_EINVAL Invalid parameters. @retval -RED_EBUSY All buffers are referenced. */ REDSTATUS RedBufferGet( uint32_t ulBlock, uint16_t uFlags, void **ppBuffer) { REDSTATUS ret = 0; uint8_t bIdx; if((ulBlock >= gpRedVolume->ulBlockCount) || ((uFlags & BFLAG_MASK) != uFlags) || (ppBuffer == NULL)) { REDERROR(); ret = -RED_EINVAL; } else { if(BufferFind(ulBlock, &bIdx)) { /* Error if the buffer exists and BFLAG_NEW was specified, since the new flag is used when a block is newly allocated/created, so the block was previously free and and there should never be an existing buffer for a free block. Error if the buffer exists but does not have the same type as was requested. */ if( ((uFlags & BFLAG_NEW) != 0U) || ((uFlags & BFLAG_META_MASK) != (gBufCtx.aHead[bIdx].uFlags & BFLAG_META_MASK))) { CRITICAL_ERROR(); ret = -RED_EFUBAR; } } else if(gBufCtx.uNumUsed == REDCONF_BUFFER_COUNT) { /* The MINIMUM_BUFFER_COUNT is supposed to ensure that no operation ever runs out of buffers, so this should never happen. */ CRITICAL_ERROR(); ret = -RED_EBUSY; } else { BUFFERHEAD *pHead; /* Search for the least recently used buffer which is not referenced. */ for(bIdx = (uint8_t)(REDCONF_BUFFER_COUNT - 1U); bIdx > 0U; bIdx--) { if(gBufCtx.aHead[gBufCtx.abMRU[bIdx]].bRefCount == 0U) { break; } } bIdx = gBufCtx.abMRU[bIdx]; pHead = &gBufCtx.aHead[bIdx]; if(pHead->bRefCount == 0U) { /* If the LRU buffer is valid and dirty, write it out before repurposing it. */ if(((pHead->uFlags & BFLAG_DIRTY) != 0U) && (pHead->ulBlock != BBLK_INVALID)) { #if REDCONF_READ_ONLY == 1 CRITICAL_ERROR(); ret = -RED_EFUBAR; #else ret = BufferWrite(bIdx); #endif } } else { /* All the buffers are used, which should have been caught by checking gBufCtx.uNumUsed. */ CRITICAL_ERROR(); ret = -RED_EBUSY; } if(ret == 0) { if((uFlags & BFLAG_NEW) == 0U) { /* Invalidate the LRU buffer. If the read fails, we do not want the buffer head to continue to refer to the old block number, since the read, even if it fails, may have partially overwritten the buffer data (consider the case where block size exceeds sector size, and some but not all of the sectors are read successfully), and if the buffer were to be used subsequently with its partially erroneous contents, bad things could happen. */ pHead->ulBlock = BBLK_INVALID; ret = RedIoRead(gbRedVolNum, ulBlock, 1U, gBufCtx.b.aabBuffer[bIdx]); if((ret == 0) && ((uFlags & BFLAG_META) != 0U)) { if(!BufferIsValid(gBufCtx.b.aabBuffer[bIdx], uFlags)) { /* A corrupt metadata node is usually a critical error. The master block is an exception since it might be invalid because the volume is not mounted; that condition is expected and should not result in an assertion. */ CRITICAL_ASSERT((uFlags & BFLAG_META_MASTER) == BFLAG_META_MASTER); ret = -RED_EIO; } } #ifdef REDCONF_ENDIAN_SWAP if(ret == 0) { BufferEndianSwap(gBufCtx.b.aabBuffer[bIdx], uFlags); } #endif } else { RedMemSet(gBufCtx.b.aabBuffer[bIdx], 0U, REDCONF_BLOCK_SIZE); } } if(ret == 0) { pHead->bVolNum = gbRedVolNum; pHead->ulBlock = ulBlock; pHead->uFlags = 0U; } } /* Reference the buffer, update its flags, and promote it to MRU. This happens both when BufferFind() found an existing buffer for the block and when the LRU buffer was repurposed to create a buffer for the block. */ if(ret == 0) { BUFFERHEAD *pHead = &gBufCtx.aHead[bIdx]; pHead->bRefCount++; if(pHead->bRefCount == 1U) { gBufCtx.uNumUsed++; } /* BFLAG_NEW tells this function to zero the buffer instead of reading it from disk; it has no meaning later on, and thus is not saved. */ pHead->uFlags |= (uFlags & (~BFLAG_NEW)); BufferMakeMRU(bIdx); *ppBuffer = gBufCtx.b.aabBuffer[bIdx]; } } 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; }