/*             ----> 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 */
