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

