/* ----> 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 directory operations. */ #include #if REDCONF_API_POSIX == 1 #include #define DIR_INDEX_INVALID UINT32_MAX #if (REDCONF_NAME_MAX % 4U) != 0U #define DIRENT_PADDING (4U - (REDCONF_NAME_MAX % 4U)) #else #define DIRENT_PADDING (0U) #endif #define DIRENT_SIZE (4U + REDCONF_NAME_MAX + DIRENT_PADDING) #define DIRENTS_PER_BLOCK (REDCONF_BLOCK_SIZE / DIRENT_SIZE) #define DIRENTS_MAX (uint32_t)REDMIN(UINT32_MAX, UINT64_SUFFIX(1) * INODE_DATA_BLOCKS * DIRENTS_PER_BLOCK) /** @brief On-disk directory entry. */ typedef struct { /** The inode number that the directory entry points at. If the directory entry is available, this holds INODE_INVALID. */ uint32_t ulInode; /** The name of the directory entry. For names shorter than REDCONF_NAME_MAX, unused bytes in the array are zeroed. For names of the maximum length, the string is not null terminated. */ char acName[REDCONF_NAME_MAX]; #if DIRENT_PADDING > 0U /** Unused padding so that ulInode is always aligned on a four-byte boundary. */ uint8_t abPadding[DIRENT_PADDING]; #endif } DIRENT; #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1) static REDSTATUS DirCyclicRenameCheck(uint32_t ulSrcInode, const CINODE *pDstPInode); #endif #if REDCONF_READ_ONLY == 0 static REDSTATUS DirEntryWrite(CINODE *pPInode, uint32_t ulIdx, uint32_t ulInode, const char *pszName, uint32_t ulNameLen); static uint64_t DirEntryIndexToOffset(uint32_t ulIdx); #endif static uint32_t DirOffsetToEntryIndex(uint64_t ullOffset); #if REDCONF_READ_ONLY == 0 /** @brief Create a new entry in a directory. @param pPInode A pointer to the cached inode structure of the directory to which the new entry will be added. @param pszName The name to be given to the new entry, terminated by a null or a path separator. @param ulInode The inode number the new name will point at. @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_ENOSPC There is not enough space on the volume to create the new directory entry; or the directory is full. @retval -RED_ENOTDIR @p pPInode is not a directory. @retval -RED_ENAMETOOLONG @p pszName is too long. @retval -RED_EEXIST @p pszName already exists in @p ulPInode. @retval -RED_EINVAL @p pPInode is not a mounted dirty cached inode structure; or @p pszName is not a valid name. */ REDSTATUS RedDirEntryCreate( CINODE *pPInode, const char *pszName, uint32_t ulInode) { REDSTATUS ret; if(!CINODE_IS_DIRTY(pPInode) || (pszName == NULL) || !INODE_IS_VALID(ulInode)) { ret = -RED_EINVAL; } else if(!pPInode->fDirectory) { ret = -RED_ENOTDIR; } else { uint32_t ulNameLen = RedNameLen(pszName); if(ulNameLen == 0U) { ret = -RED_EINVAL; } else if(ulNameLen > REDCONF_NAME_MAX) { ret = -RED_ENAMETOOLONG; } else { uint32_t ulEntryIdx; ret = RedDirEntryLookup(pPInode, pszName, &ulEntryIdx, NULL); if(ret == 0) { ret = -RED_EEXIST; } else if(ret == -RED_ENOENT) { if(ulEntryIdx == DIR_INDEX_INVALID) { ret = -RED_ENOSPC; } else { ret = 0; } } else { /* Unexpected error, no action. */ } if(ret == 0) { ret = DirEntryWrite(pPInode, ulEntryIdx, ulInode, pszName, ulNameLen); } } } return ret; } #endif /* REDCONF_READ_ONLY == 0 */ #if DELETE_SUPPORTED /** @brief Delete an existing directory entry. @param pPInode A pointer to the cached inode structure of the directory containing the entry to be deleted. @param ulDeleteIdx Position within the directory of the entry to be deleted. @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_ENOSPC The file system does not have enough space to modify the parent directory to perform the deletion. @retval -RED_ENOTDIR @p pPInode is not a directory. @retval -RED_EINVAL @p pPInode is not a mounted dirty cached inode structure; or @p ulIdx is out of range. */ REDSTATUS RedDirEntryDelete( CINODE *pPInode, uint32_t ulDeleteIdx) { REDSTATUS ret = 0; if(!CINODE_IS_DIRTY(pPInode) || (ulDeleteIdx >= DIRENTS_MAX)) { ret = -RED_EINVAL; } else if(!pPInode->fDirectory) { ret = -RED_ENOTDIR; } else if((DirEntryIndexToOffset(ulDeleteIdx) + DIRENT_SIZE) == pPInode->pInodeBuf->ullSize) { /* Start searching one behind the index to be deleted. */ uint32_t ulTruncIdx = ulDeleteIdx - 1U; bool fDone = false; /* We are deleting the last dirent in the directory, so search backwards to find the last populated dirent, allowing us to truncate the directory to that point. */ while((ret == 0) && (ulTruncIdx != UINT32_MAX) && !fDone) { ret = RedInodeDataSeekAndRead(pPInode, ulTruncIdx / DIRENTS_PER_BLOCK); if(ret == 0) { const DIRENT *pDirents = CAST_CONST_DIRENT_PTR(pPInode->pbData); uint32_t ulBlockIdx = ulTruncIdx % DIRENTS_PER_BLOCK; do { if(pDirents[ulBlockIdx].ulInode != INODE_INVALID) { fDone = true; break; } ulTruncIdx--; ulBlockIdx--; } while(ulBlockIdx != UINT32_MAX); } else if(ret == -RED_ENODATA) { ret = 0; REDASSERT((ulTruncIdx % DIRENTS_PER_BLOCK) == 0U); ulTruncIdx -= DIRENTS_PER_BLOCK; } else { /* Unexpected error, loop will terminate; nothing else to be done. */ } } /* Currently ulTruncIdx represents the last valid dirent index, or UINT32_MAX if the directory is now empty. Increment it so that it represents the first invalid entry, which will be truncated. */ ulTruncIdx++; /* Truncate the directory, deleting the requested entry and any empty dirents at the end of the directory. */ if(ret == 0) { ret = RedInodeDataTruncate(pPInode, DirEntryIndexToOffset(ulTruncIdx)); } } else { /* The dirent to delete is not the last entry in the directory, so just zero it. */ ret = DirEntryWrite(pPInode, ulDeleteIdx, INODE_INVALID, "", 0U); } return ret; } #endif /* DELETE_SUPPORTED */ /** @brief Perform a case-sensitive search of a directory for a given name. If found, then position of the entry within the directory and the inode number it points to are returned. As an optimization for directory entry creation, in the case where the requested entry does not exist, the position of the first available (unused) entry is returned. @param pPInode A pointer to the cached inode structure of the directory to search. @param pszName The name of the desired entry, terminated by either a null or a path separator. @param pulEntryIdx On successful return, meaning that the desired entry exists, populated with the position of the entry. If returning an -RED_ENOENT error, populated with the position of the first available entry, or set to DIR_INDEX_INVALID if the directory is full. Optional. @param pulInode On successful return, populated with the inode number that the name points to. Optional; may be `NULL`. @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_ENOENT @p pszName does not name an existing file or directory. @retval -RED_ENOTDIR @p pPInode is not a directory. @retval -RED_EINVAL @p pPInode is not a mounted cached inode structure; or @p pszName is not a valid name; or @p pulEntryIdx is `NULL`. @retval -RED_ENAMETOOLONG @p pszName is too long. */ REDSTATUS RedDirEntryLookup( CINODE *pPInode, const char *pszName, uint32_t *pulEntryIdx, uint32_t *pulInode) { REDSTATUS ret = 0; if(!CINODE_IS_MOUNTED(pPInode) || (pszName == NULL)) { ret = -RED_EINVAL; } else if(!pPInode->fDirectory) { ret = -RED_ENOTDIR; } else { uint32_t ulNameLen = RedNameLen(pszName); if(ulNameLen == 0U) { ret = -RED_EINVAL; } else if(ulNameLen > REDCONF_NAME_MAX) { ret = -RED_ENAMETOOLONG; } else { uint32_t ulIdx = 0U; uint32_t ulDirentCount = DirOffsetToEntryIndex(pPInode->pInodeBuf->ullSize); uint32_t ulFreeIdx = DIR_INDEX_INVALID; /* Index of first free dirent. */ /* Loop over the directory blocks, searching each block for a dirent that matches the given name. */ while((ret == 0) && (ulIdx < ulDirentCount)) { ret = RedInodeDataSeekAndRead(pPInode, ulIdx / DIRENTS_PER_BLOCK); if(ret == 0) { const DIRENT *pDirents = CAST_CONST_DIRENT_PTR(pPInode->pbData); uint32_t ulBlockLastIdx = REDMIN(DIRENTS_PER_BLOCK, ulDirentCount - ulIdx); uint32_t ulBlockIdx; for(ulBlockIdx = 0U; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++) { const DIRENT *pDirent = &pDirents[ulBlockIdx]; if(pDirent->ulInode != INODE_INVALID) { /* The name in the dirent will not be null terminated if it is of the maximum length, so use a bounded string compare and then make sure there is nothing more to the name. */ if( (RedStrNCmp(pDirent->acName, pszName, ulNameLen) == 0) && ((ulNameLen == REDCONF_NAME_MAX) || (pDirent->acName[ulNameLen] == '\0'))) { /* Found a matching dirent, stop and return its information. */ if(pulInode != NULL) { *pulInode = pDirent->ulInode; #ifdef REDCONF_ENDIAN_SWAP *pulInode = RedRev32(*pulInode); #endif } ulIdx += ulBlockIdx; break; } } else if(ulFreeIdx == DIR_INDEX_INVALID) { ulFreeIdx = ulIdx + ulBlockIdx; } else { /* The directory entry is free, but we already found a free one, so there's nothing to do here. */ } } if(ulBlockIdx < ulBlockLastIdx) { /* If we broke out of the for loop, we found a matching dirent and can stop the search. */ break; } ulIdx += ulBlockLastIdx; } else if(ret == -RED_ENODATA) { if(ulFreeIdx == DIR_INDEX_INVALID) { ulFreeIdx = ulIdx; } ret = 0; ulIdx += DIRENTS_PER_BLOCK; } else { /* Unexpected error, let the loop terminate, no action here. */ } } if(ret == 0) { /* If we made it all the way to the end of the directory without stopping, then the given name does not exist in the directory. */ if(ulIdx == ulDirentCount) { /* If the directory had no sparse dirents, then the first free dirent is beyond the end of the directory. If the directory is already the maximum size, then there is no free dirent. */ if((ulFreeIdx == DIR_INDEX_INVALID) && (ulDirentCount < DIRENTS_MAX)) { ulFreeIdx = ulDirentCount; } ulIdx = ulFreeIdx; ret = -RED_ENOENT; } if(pulEntryIdx != NULL) { *pulEntryIdx = ulIdx; } } } } return ret; } #if (REDCONF_API_POSIX_READDIR == 1) || (REDCONF_CHECKER == 1) /** @brief Read the next entry from a directory, given a starting index. @param pPInode A pointer to the cached inode structure of the directory to read from. @param pulIdx On entry, the directory index to start reading from. On successful return, populated with the directory index to use for subsequent reads. On -RED_ENOENT return, populated with the directory index immediately following the last valid one. @param pszName On successful return, populated with the name of the next directory entry. Buffer must be at least REDCONF_NAME_MAX + 1 in size, to store the maximum name length plus a null terminator. @param pulInode On successful return, populated with the inode number pointed at by the next directory entry. @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_ENOENT There are no more entries in the directory. @retval -RED_ENOTDIR @p pPInode is not a directory. @retval -RED_EINVAL @p pPInode is not a mounted cached inode structure; or @p pszName is `NULL`; or @p pulIdx is `NULL`; or @p pulInode is `NULL`. */ REDSTATUS RedDirEntryRead( CINODE *pPInode, uint32_t *pulIdx, char *pszName, uint32_t *pulInode) { REDSTATUS ret = 0; if(!CINODE_IS_MOUNTED(pPInode) || (pulIdx == NULL) || (pszName == NULL) || (pulInode == NULL)) { ret = -RED_EINVAL; } else if(!pPInode->fDirectory) { ret = -RED_ENOTDIR; } else { uint32_t ulIdx = *pulIdx; uint32_t ulDirentCount = DirOffsetToEntryIndex(pPInode->pInodeBuf->ullSize); /* Starting either at the beginning of the directory or where we left off, loop over the directory blocks, searching each block for a non-sparse dirent to return as the next entry in the directory. */ while((ret == 0) && (ulIdx < ulDirentCount)) { uint32_t ulBlockOffset = ulIdx / DIRENTS_PER_BLOCK; ret = RedInodeDataSeekAndRead(pPInode, ulBlockOffset); if(ret == 0) { const DIRENT *pDirents = CAST_CONST_DIRENT_PTR(pPInode->pbData); uint32_t ulBlockLastIdx = REDMIN(DIRENTS_PER_BLOCK, ulDirentCount - (ulBlockOffset * DIRENTS_PER_BLOCK)); uint32_t ulBlockIdx; for(ulBlockIdx = ulIdx % DIRENTS_PER_BLOCK; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++) { if(pDirents[ulBlockIdx].ulInode != INODE_INVALID) { *pulIdx = ulIdx + 1U; RedStrNCpy(pszName, pDirents[ulBlockIdx].acName, REDCONF_NAME_MAX); pszName[REDCONF_NAME_MAX] = '\0'; *pulInode = pDirents[ulBlockIdx].ulInode; #ifdef REDCONF_ENDIAN_SWAP *pulInode = RedRev32(*pulInode); #endif break; } ulIdx++; } if(ulBlockIdx < ulBlockLastIdx) { REDASSERT(ulIdx <= ulDirentCount); break; } } else if(ret == -RED_ENODATA) { ulIdx += DIRENTS_PER_BLOCK - (ulIdx % DIRENTS_PER_BLOCK); ret = 0; } else { /* Unexpected error, loop will terminate; nothing else to do. */ } REDASSERT(ulIdx <= ulDirentCount); } if((ret == 0) && (ulIdx >= ulDirentCount)) { *pulIdx = ulDirentCount; ret = -RED_ENOENT; } } return ret; } #endif #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1) /** Rename a directory entry. @param pSrcPInode The inode of the directory containing @p pszSrcName. @param pszSrcName The name of the directory entry to be renamed. @param pSrcInode On successful return, populated with the inode of the source entry. @param pDstPInode The inode of the directory in which @p pszDstName will be created or replaced. @param pszDstName The name of the directory entry to be created or replaced. @param pDstInode On successful return, if the destination previously existed, populated with the inode previously pointed to by the destination. This may be the same as the source inode. If the destination did not exist, populated with INODE_INVALID. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EEXIST #REDCONF_RENAME_ATOMIC is false and the destination name exists. @retval -RED_EINVAL @p pSrcPInode is not a mounted dirty cached inode structure; or @p pSrcInode is `NULL`; or @p pszSrcName is not a valid name; or @p pDstPInode is not a mounted dirty cached inode structure; or @p pDstInode is `NULL`; or @p pszDstName is not a valid name. @retval -RED_EIO A disk I/O error occurred. @retval -RED_EISDIR The destination name exists and is a directory, and the source name is a non-directory. @retval -RED_ENAMETOOLONG Either @p pszSrcName or @p pszDstName is longer than #REDCONF_NAME_MAX. @retval -RED_ENOENT The source name is not an existing entry; or either @p pszSrcName or @p pszDstName point to an empty string. @retval -RED_ENOTDIR @p pSrcPInode is not a directory; or @p pDstPInode is not a directory; or the source name is a directory and the destination name is a file. @retval -RED_ENOTEMPTY The destination name is a directory which is not empty. @retval -RED_ENOSPC The file system does not have enough space to extend the @p ulDstPInode directory. @retval -RED_EROFS The directory to be removed resides on a read-only file system. */ REDSTATUS RedDirEntryRename( CINODE *pSrcPInode, const char *pszSrcName, CINODE *pSrcInode, CINODE *pDstPInode, const char *pszDstName, CINODE *pDstInode) { REDSTATUS ret; if( !CINODE_IS_DIRTY(pSrcPInode) || (pszSrcName == NULL) || (pSrcInode == NULL) || !CINODE_IS_DIRTY(pDstPInode) || (pszDstName == NULL) || (pDstInode == NULL)) { ret = -RED_EINVAL; } else if(!pSrcPInode->fDirectory || !pDstPInode->fDirectory) { ret = -RED_ENOTDIR; } else { uint32_t ulDstIdx = 0U; /* Init'd to quiet warnings. */ uint32_t ulSrcIdx; /* Look up the source and destination names. */ ret = RedDirEntryLookup(pSrcPInode, pszSrcName, &ulSrcIdx, &pSrcInode->ulInode); if(ret == 0) { ret = RedDirEntryLookup(pDstPInode, pszDstName, &ulDstIdx, &pDstInode->ulInode); if(ret == -RED_ENOENT) { if(ulDstIdx == DIR_INDEX_INVALID) { ret = -RED_ENOSPC; } else { #if REDCONF_RENAME_ATOMIC == 1 pDstInode->ulInode = INODE_INVALID; #endif ret = 0; } } #if REDCONF_RENAME_ATOMIC == 0 else if(ret == 0) { ret = -RED_EEXIST; } else { /* Nothing to do here, just propagate the error. */ } #endif } #if REDCONF_RENAME_ATOMIC == 1 /* If both names point to the same inode, POSIX says to do nothing to either name. */ if((ret == 0) && (pSrcInode->ulInode != pDstInode->ulInode)) #else if(ret == 0) #endif { ret = RedInodeMount(pSrcInode, FTYPE_EITHER, true); #if REDCONF_RENAME_ATOMIC == 1 if((ret == 0) && (pDstInode->ulInode != INODE_INVALID)) { /* Source and destination must be the same type (file/dir). */ ret = RedInodeMount(pDstInode, pSrcInode->fDirectory ? FTYPE_DIR : FTYPE_FILE, true); /* If renaming directories, the destination must be empty. */ if((ret == 0) && pDstInode->fDirectory && (pDstInode->pInodeBuf->ullSize > 0U)) { ret = -RED_ENOTEMPTY; } } #endif /* If we are renaming a directory, make sure the rename isn't cyclic (e.g., renaming "foo" into "foo/bar"). */ if((ret == 0) && pSrcInode->fDirectory) { ret = DirCyclicRenameCheck(pSrcInode->ulInode, pDstPInode); } if(ret == 0) { ret = DirEntryWrite(pDstPInode, ulDstIdx, pSrcInode->ulInode, pszDstName, RedNameLen(pszDstName)); } if(ret == 0) { ret = RedDirEntryDelete(pSrcPInode, ulSrcIdx); if(ret == -RED_ENOSPC) { REDSTATUS ret2; /* If there was not enough space to branch the parent directory inode and data block containin the source entry, revert destination directory entry to its previous state. */ #if REDCONF_RENAME_ATOMIC == 1 if(pDstInode->ulInode != INODE_INVALID) { ret2 = DirEntryWrite(pDstPInode, ulDstIdx, pDstInode->ulInode, pszDstName, RedNameLen(pszDstName)); } else #endif { ret2 = RedDirEntryDelete(pDstPInode, ulDstIdx); } if(ret2 != 0) { ret = ret2; CRITICAL_ERROR(); } } } if(ret == 0) { pSrcInode->pInodeBuf->ulPInode = pDstPInode->ulInode; } } } return ret; } /** @brief Check for a cyclic rename. A cyclic rename is renaming a directory into a subdirectory of itself. For example, renaming "a" into "a/b/c/d" is cyclic. These renames must not be allowed since they would corrupt the directory tree. @param ulSrcInode The inode number of the directory being renamed. @param pDstPInode A pointer to the cached inode structure of the directory into which the source is being renamed. @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 The rename is cyclic; or invalid parameters. @retval -RED_ENOTDIR @p pDstPInode is not a directory. */ static REDSTATUS DirCyclicRenameCheck( uint32_t ulSrcInode, const CINODE *pDstPInode) { REDSTATUS ret = 0; if(!INODE_IS_VALID(ulSrcInode) || !CINODE_IS_MOUNTED(pDstPInode)) { REDERROR(); ret = -RED_EINVAL; } else if(ulSrcInode == pDstPInode->ulInode) { ret = -RED_EINVAL; } else if(!pDstPInode->fDirectory) { ret = -RED_ENOTDIR; } else { CINODE NextParent; /* Used to prevent infinite loop in case of corrupted directory structure. */ uint32_t ulIteration = 0U; NextParent.ulInode = pDstPInode->pInodeBuf->ulPInode; while( (NextParent.ulInode != ulSrcInode) && (NextParent.ulInode != INODE_ROOTDIR) && (NextParent.ulInode != INODE_INVALID) && (ulIteration < gpRedVolConf->ulInodeCount)) { ret = RedInodeMount(&NextParent, FTYPE_DIR, false); if(ret != 0) { break; } NextParent.ulInode = NextParent.pInodeBuf->ulPInode; RedInodePut(&NextParent, 0U); ulIteration++; } if((ret == 0) && (ulIteration == gpRedVolConf->ulInodeCount)) { CRITICAL_ERROR(); ret = -RED_EFUBAR; } if((ret == 0) && (ulSrcInode == NextParent.ulInode)) { ret = -RED_EINVAL; } } return ret; } #endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1) */ #if REDCONF_READ_ONLY == 0 /** @brief Update the contents of a directory entry. @param pPInode A pointer to the cached inode structure of the directory whose entry is being written. @param ulIdx The index of the directory entry to write. @param ulInode The inode number the directory entry is to point at. @param pszName The name of the directory entry. @param ulNameLen The length of @p pszName. @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_ENOSPC There is not enough space on the volume to write the directory entry. @retval -RED_ENOTDIR @p pPInode is not a directory. @retval -RED_EINVAL Invalid parameters. */ static REDSTATUS DirEntryWrite( CINODE *pPInode, uint32_t ulIdx, uint32_t ulInode, const char *pszName, uint32_t ulNameLen) { REDSTATUS ret; if( !CINODE_IS_DIRTY(pPInode) || (ulIdx >= DIRENTS_MAX) || (!INODE_IS_VALID(ulInode) && (ulInode != INODE_INVALID)) || (pszName == NULL) || (ulNameLen > REDCONF_NAME_MAX) || ((ulNameLen == 0U) != (ulInode == INODE_INVALID))) { REDERROR(); ret = -RED_EINVAL; } else if(!pPInode->fDirectory) { ret = -RED_ENOTDIR; } else { uint64_t ullOffset = DirEntryIndexToOffset(ulIdx); uint32_t ulLen = DIRENT_SIZE; static DIRENT de; RedMemSet(&de, 0U, sizeof(de)); de.ulInode = ulInode; #ifdef REDCONF_ENDIAN_SWAP de.ulInode = RedRev32(de.ulInode); #endif RedStrNCpy(de.acName, pszName, ulNameLen); ret = RedInodeDataWrite(pPInode, ullOffset, &ulLen, &de); } return ret; } /** @brief Convert a directory entry index to a byte offset. @param ulIdx Directory entry index. @return Byte offset in the directory corresponding with ulIdx. */ static uint64_t DirEntryIndexToOffset( uint32_t ulIdx) { uint32_t ulBlock = ulIdx / DIRENTS_PER_BLOCK; uint32_t ulOffsetInBlock = ulIdx % DIRENTS_PER_BLOCK; uint64_t ullOffset; REDASSERT(ulIdx < DIRENTS_MAX); ullOffset = (uint64_t)ulBlock << BLOCK_SIZE_P2; ullOffset += (uint64_t)ulOffsetInBlock * DIRENT_SIZE; return ullOffset; } #endif /* REDCONF_READ_ONLY == 0 */ /** @brief Convert a byte offset to a directory entry index. @param ullOffset Byte offset in the directory. @return Directory entry index corresponding with @p ullOffset. */ static uint32_t DirOffsetToEntryIndex( uint64_t ullOffset) { uint32_t ulIdx; REDASSERT(ullOffset < INODE_SIZE_MAX); REDASSERT(((uint32_t)(ullOffset & (REDCONF_BLOCK_SIZE - 1U)) % DIRENT_SIZE) == 0U); /* Avoid doing any 64-bit divides. */ ulIdx = (uint32_t)(ullOffset >> BLOCK_SIZE_P2) * DIRENTS_PER_BLOCK; ulIdx += (uint32_t)(ullOffset & (REDCONF_BLOCK_SIZE - 1U)) / DIRENT_SIZE; return ulIdx; } #endif /* REDCONF_API_POSIX == 1 */