mirror of
https://github.com/FreeRTOS/FreeRTOS.git
synced 2025-08-24 19:31:03 +08:00

* Use new version of CI-CD Actions, checkout@v3 instead of checkout@v2 on all jobs * Use cSpell spell check, and use ubuntu-20.04 for formatting check * Add in bot formatting action * Update freertos_demo.yml and freertos_plus_demo.yml files to increase github log readability * Add in a Qemu demo onto the workflows.
967 lines
38 KiB
C
967 lines
38 KiB
C
/* ----> 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 <redfs.h>
|
|
|
|
#if REDCONF_API_POSIX == 1
|
|
|
|
#include <redcore.h>
|
|
|
|
|
|
#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 */
|