/* ----> 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_API_POSIX_READDIR == 1 ) || ( REDCONF_CHECKER == 1 ) */ #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 REDCONF_RENAME_ATOMIC == 1 */ /* 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 containing 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 */