/*             ----> 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 inode functions.
*/
#include <redfs.h>
#include <redcore.h>


#if REDCONF_READ_ONLY == 0
static REDSTATUS InodeIsBranched(uint32_t ulInode, bool *pfIsBranched);
#endif
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1)
static REDSTATUS InodeFindFree(uint32_t *pulInode);
#endif
#if REDCONF_READ_ONLY == 0
static REDSTATUS InodeGetWriteableCopy(uint32_t ulInode, uint8_t *pbWhich);
#endif
static REDSTATUS InodeGetCurrentCopy(uint32_t ulInode, uint8_t *pbWhich);
#if REDCONF_READ_ONLY == 0
static REDSTATUS InodeBitSet(uint32_t ulInode, uint8_t bWhich, bool fAllocated);
#endif
static uint32_t InodeBlock(uint32_t ulInode, uint8_t bWhich);


/** @brief Mount an existing inode.

    Will populate all fields of the cached inode structure, except those which
    are populated during seek.

    @param pInode   A pointer to the cached inode structure.  The
                    pInode->ulInode field must already be initialized with the
                    inode number to mount.  All other fields will be discarded.
    @param type     The expected inode type.
    @param fBranch  Whether to branch the inode.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0               Operation was successful.
    @retval -RED_EINVAL     Invalid parameters.
    @retval -RED_EROFS      @p fBranch is true but the driver is read-only.
    @retval -RED_EIO        A disk I/O error occurred.
    @retval -RED_EBADF      The inode number is free; or the inode number is not
                            valid.
    @retval -RED_EISDIR     @p type is ::FTYPE_FILE and the inode is a directory.
    @retval -RED_ENOTDIR    @p type is ::FTYPE_DIR and the inode is a file.
*/
REDSTATUS RedInodeMount(
    CINODE     *pInode,
    FTYPE       type,
    bool        fBranch)
{
    REDSTATUS   ret = 0;

    if(pInode == NULL)
    {
        ret = -RED_EINVAL;
    }
    else if(!INODE_IS_VALID(pInode->ulInode))
    {
        ret = -RED_EBADF;
    }
  #if REDCONF_API_FSE == 1
    else if(type == FTYPE_DIR)
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
  #endif
  #if REDCONF_READ_ONLY == 1
    else if(fBranch)
    {
        REDERROR();
        ret = -RED_EROFS;
    }
  #endif
    else
    {
        uint32_t ulInode = pInode->ulInode;
        uint8_t  bWhich = 0U; /* Init'd to quiet warnings. */

        RedMemSet(pInode, 0U, sizeof(*pInode));
        pInode->ulInode = ulInode;

        ret = InodeGetCurrentCopy(pInode->ulInode, &bWhich);

        if(ret == 0)
        {
            ret = RedBufferGet(InodeBlock(pInode->ulInode, bWhich), BFLAG_META_INODE, CAST_VOID_PTR_PTR(&pInode->pInodeBuf));
        }

      #if REDCONF_READ_ONLY == 0
        if(ret == 0)
        {
            ret = InodeIsBranched(pInode->ulInode, &pInode->fBranched);
        }
      #endif

        if(ret == 0)
        {
            if(RED_S_ISREG(pInode->pInodeBuf->uMode))
            {
              #if REDCONF_API_POSIX == 1
                pInode->fDirectory = false;

                if(type == FTYPE_DIR)
                {
                    ret = -RED_ENOTDIR;
                }
              #endif
            }
          #if REDCONF_API_POSIX == 1
            else if(RED_S_ISDIR(pInode->pInodeBuf->uMode))
            {
                pInode->fDirectory = true;

                if(type == FTYPE_FILE)
                {
                    ret = -RED_EISDIR;
                }
            }
          #endif
            else
            {
                /*  Missing or unsupported inode type.
                */
                CRITICAL_ERROR();
                ret = -RED_EFUBAR;
            }
        }

      #if REDCONF_READ_ONLY == 0
        if((ret == 0) && fBranch)
        {
            ret = RedInodeBranch(pInode);
        }
      #endif

        if(ret != 0)
        {
            RedInodePut(pInode, 0U);
        }
    }

    return ret;
}


#if (REDCONF_READ_ONLY == 0) && ((REDCONF_API_POSIX == 1) || FORMAT_SUPPORTED)
/** @brief Create an inode.

    @param pInode   Pointer to the cached inode structure.  If pInode->ulInode
                    is #INODE_INVALID, a free inode will be found; otherwise,
                    pInode->ulInode will be the inode number (an error will be
                    returned if it is not free).
    @param ulPInode The parent inode number.
    @param uMode    The inode mode.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EBADF  pInode->ulInode is an invalid inode number other than
                        #INODE_INVALID.
    @retval -RED_EINVAL Invalid parameters.
    @retval -RED_EEXIST Tried to create an inode with an inode number that is
                        already in use.
    @retval -RED_ENFILE All inode slots are already in use.
    @retval -RED_EIO    A disk I/O error occurred.
*/
REDSTATUS RedInodeCreate(
    CINODE     *pInode,
    uint32_t    ulPInode,
    uint16_t    uMode)
{
    REDSTATUS   ret;

  #if REDCONF_API_POSIX == 1
    /*  ulPInode must be a valid inode number, unless we are creating the root
        directory, in which case ulPInode must be INODE_INVALID (the root
        directory has no parent).
    */
    if(    (pInode == NULL)
        || (!INODE_IS_VALID(ulPInode) && ((ulPInode != INODE_INVALID) || (pInode->ulInode != INODE_ROOTDIR))))
  #else
    if(pInode == NULL)
  #endif
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
    else
    {
        uint32_t ulInode = pInode->ulInode;

        RedMemSet(pInode, 0U, sizeof(*pInode));

      #if REDCONF_API_POSIX == 1
        if(ulInode == INODE_INVALID)
        {
            /*  Caller requested that an inode number be allocated.  Search for
                an unused inode number, error if there isn't one.
            */
            ret = InodeFindFree(&pInode->ulInode);
        }
        else
      #endif
        {
            /*  Caller requested creation of a specific inode number.  Make sure
                it's valid and doesn't already exist.
            */
            if(INODE_IS_VALID(ulInode))
            {
                bool fFree;

                ret = RedInodeIsFree(ulInode, &fFree);
                if(ret == 0)
                {
                    if(fFree)
                    {
                        pInode->ulInode = ulInode;
                    }
                    else
                    {
                        ret = -RED_EEXIST;
                    }
                }
            }
            else
            {
                ret = -RED_EBADF;
            }
        }

        if(ret == 0)
        {
            uint8_t bWriteableWhich;

            ret = InodeGetWriteableCopy(pInode->ulInode, &bWriteableWhich);

            if(ret == 0)
            {
                ret = RedBufferGet(InodeBlock(pInode->ulInode, bWriteableWhich),
                    (uint16_t)((uint32_t)BFLAG_META_INODE | BFLAG_DIRTY | BFLAG_NEW), CAST_VOID_PTR_PTR(&pInode->pInodeBuf));

                if(ret == 0)
                {
                    /*  Mark the inode block as allocated.
                    */
                    ret = InodeBitSet(pInode->ulInode, bWriteableWhich, true);

                    if(ret != 0)
                    {
                        RedBufferPut(pInode->pInodeBuf);
                    }
                }
            }
        }

        if(ret == 0)
        {
          #if REDCONF_INODE_TIMESTAMPS == 1
            uint32_t ulNow = RedOsClockGetTime();

            pInode->pInodeBuf->ulATime = ulNow;
            pInode->pInodeBuf->ulMTime = ulNow;
            pInode->pInodeBuf->ulCTime = ulNow;
          #endif

            pInode->pInodeBuf->uMode = uMode;

          #if REDCONF_API_POSIX == 1
          #if REDCONF_API_POSIX_LINK == 1
            pInode->pInodeBuf->uNLink = 1U;
          #endif
            pInode->pInodeBuf->ulPInode = ulPInode;
          #else
            (void)ulPInode;
          #endif

            pInode->fBranched = true;
            pInode->fDirty = true;

          #if REDCONF_API_POSIX == 1
            gpRedMR->ulFreeInodes--;
          #endif
        }
    }

    return ret;
}
#endif /* (REDCONF_READ_ONLY == 0) && ((REDCONF_API_POSIX == 1) || FORMAT_SUPPORTED) */


#if DELETE_SUPPORTED
/** @brief Delete an inode.

    @param pInode   Pointer to the cached inode structure.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EBADF  The inode is free.
    @retval -RED_EINVAL @p pInode is `NULL`; or pInode->pBuffer is `NULL`.
    @retval -RED_EIO    A disk I/O error occurred.
*/
REDSTATUS RedInodeDelete(
    CINODE     *pInode)
{
    REDSTATUS   ret = 0;

    if(!CINODE_IS_MOUNTED(pInode))
    {
        ret = -RED_EINVAL;
    }
    else
    {
        if(pInode->pInodeBuf->ullSize != 0U)
        {
            ret = RedInodeDataTruncate(pInode, UINT64_SUFFIX(0));
        }

        if(ret == 0)
        {
            ret = RedInodeFree(pInode);
        }
    }

    return ret;
}


/** @brief Decrement an inode link count and delete the inode if the link count
           falls to zero.

    @param pInode   A pointer to the cached inode structure.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EINVAL @p pInode is not a mounted cachde inode.
    @retval -RED_EIO    A disk I/O error occurred.
*/
REDSTATUS RedInodeLinkDec(
    CINODE     *pInode)
{
    REDSTATUS   ret;

    if(!CINODE_IS_MOUNTED(pInode))
    {
        ret = -RED_EINVAL;
    }
  #if REDCONF_API_POSIX_LINK == 1
    else if(pInode->pInodeBuf->uNLink > 1U)
    {
        ret = RedInodeBranch(pInode);

        if(ret == 0)
        {
            pInode->pInodeBuf->uNLink--;
        }
    }
  #endif
    else
    {
        ret = RedInodeDelete(pInode);
    }

    return ret;
}
#endif /* DELETE_SUPPORTED */


#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1)
/** @brief Free an inode.

    @param pInode   Pointer to the cached inode structure.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EBADF  The inode is free.
    @retval -RED_EINVAL @p pInode is `NULL`; or pInode->pBuffer is `NULL`.
    @retval -RED_EIO    A disk I/O error occurred.
*/
REDSTATUS RedInodeFree(
    CINODE     *pInode)
{
    REDSTATUS   ret;

    if(!CINODE_IS_MOUNTED(pInode))
    {
        ret = -RED_EINVAL;
    }
    else
    {
        bool fSlot0Allocated;

        RedBufferDiscard(pInode->pInodeBuf);
        pInode->pInodeBuf = NULL;

        /*  Determine which of the two slots for the inode is currently
            allocated, and free that slot.
        */
        ret = RedInodeBitGet(gpRedCoreVol->bCurMR, pInode->ulInode, 0U, &fSlot0Allocated);

        if(ret == 0)
        {
            bool fSlot1Allocated;

            ret = RedInodeBitGet(gpRedCoreVol->bCurMR, pInode->ulInode, 1U, &fSlot1Allocated);

            if(ret == 0)
            {
                if(fSlot0Allocated)
                {
                    if(fSlot1Allocated)
                    {
                        /*  Both inode slots should never be allocated at
                            the same time.
                        */
                        CRITICAL_ERROR();
                        ret = -RED_EFUBAR;
                    }
                    else
                    {
                        ret = InodeBitSet(pInode->ulInode, 0U, false);
                    }
                }
                else
                {
                    if(!fSlot1Allocated)
                    {
                        /*  The inode in unallocated, which should have been
                            caught when it was mounted.
                        */
                        CRITICAL_ERROR();
                        ret = -RED_EBADF;
                    }
                    else
                    {
                        ret = InodeBitSet(pInode->ulInode, 1U, false);
                    }
                }
            }
        }

        pInode->ulInode = INODE_INVALID;

        if(ret == 0)
        {
            if(gpRedMR->ulFreeInodes >= gpRedVolConf->ulInodeCount)
            {
                CRITICAL_ERROR();
                ret = -RED_EFUBAR;
            }
            else
            {
                gpRedMR->ulFreeInodes++;
            }
        }
    }

    return ret;
}
#endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) */


/** @brief Put the cached inode structure.

    This puts all of the buffers in the ::CINODE structure.  Also updates inode
    timestamp fields if requested.

    @param pInode       The cached inode structure.
    @param bTimeFields  The inode timestamp fields to update.
*/
void RedInodePut(
    CINODE  *pInode,
    uint8_t  bTimeFields)
{
    if(pInode == NULL)
    {
        REDERROR();
    }
    else
    {
        RedInodePutCoord(pInode);

        if(pInode->pInodeBuf != NULL)
        {
          #if (REDCONF_READ_ONLY == 0) && (REDCONF_INODE_TIMESTAMPS == 1)
            if((bTimeFields & IPUT_UPDATE_MASK) != 0U)
            {
                if(!pInode->fBranched || !pInode->fDirty)
                {
                    REDERROR();
                }
                else
                {
                    uint32_t ulNow = RedOsClockGetTime();

                  #if REDCONF_ATIME == 1
                    if((bTimeFields & IPUT_UPDATE_ATIME) != 0U)
                    {
                        pInode->pInodeBuf->ulATime = ulNow;
                    }
                  #endif

                    if((bTimeFields & IPUT_UPDATE_MTIME) != 0U)
                    {
                        pInode->pInodeBuf->ulMTime = ulNow;
                    }

                    if((bTimeFields & IPUT_UPDATE_CTIME) != 0U)
                    {
                        pInode->pInodeBuf->ulCTime = ulNow;
                    }
                }
            }
          #else
            (void)bTimeFields;
          #endif

            RedBufferPut(pInode->pInodeBuf);
            pInode->pInodeBuf = NULL;
        }
    }
}


/** @brief Put all buffers in the cached inode structure except for the inode
           node buffer.

    @param pInode   A pointer to the cached inode structure.
*/
void RedInodePutCoord(
    CINODE *pInode)
{
    if(pInode == NULL)
    {
        REDERROR();
    }
    else
    {
        RedInodePutData(pInode);
      #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
        RedInodePutIndir(pInode);
      #endif
      #if DINDIR_POINTERS > 0U
        RedInodePutDindir(pInode);
      #endif
    }
}


#if DINDIR_POINTERS > 0U
/** @brief Put the double indirect buffer.

    @param pInode   A pointer to the cached inode structure.
*/
void RedInodePutDindir(
    CINODE *pInode)
{
    if(pInode == NULL)
    {
        REDERROR();
    }
    else if(pInode->pDindir != NULL)
    {
        RedBufferPut(pInode->pDindir);
        pInode->pDindir = NULL;
    }
    else
    {
        /*  No double indirect buffer, nothing to put.
        */
    }
}
#endif


#if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
/** @brief Put the indirect buffer.

    @param pInode   A pointer to the cached inode structure.
*/
void RedInodePutIndir(
    CINODE *pInode)
{
    if(pInode == NULL)
    {
        REDERROR();
    }
    else if(pInode->pIndir != NULL)
    {
        RedBufferPut(pInode->pIndir);
        pInode->pIndir = NULL;
    }
    else
    {
        /*  No indirect buffer, nothing to put.
        */
    }
}
#endif


/** @brief Put the inode data buffer.

    @param pInode   A pointer to the cached inode structure.
*/
void RedInodePutData(
    CINODE *pInode)
{
    if(pInode == NULL)
    {
        REDERROR();
    }
    else if(pInode->pbData != NULL)
    {
        RedBufferPut(pInode->pbData);
        pInode->pbData = NULL;
    }
    else
    {
        /*  No data buffer, nothing to put.
        */
    }
}


#if REDCONF_READ_ONLY == 0
/** @brief Determine if an inode is branched.

    @param ulInode      The inode number to examine.
    @param pfIsBranched On successful return, populated with whether the inode
                        is branched.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EINVAL @p pInode is `NULL`; or @p ulInode is not a valid inode
                        number.
    @retval -RED_EIO    A disk I/O error occurred.
*/
static REDSTATUS InodeIsBranched(
    uint32_t    ulInode,
    bool       *pfIsBranched)
{
    REDSTATUS   ret;

    if(!INODE_IS_VALID(ulInode) || (pfIsBranched == NULL))
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
    else
    {
        ALLOCSTATE state;

        ret = RedImapBlockState(InodeBlock(ulInode, 0U), &state);

        if(ret == 0)
        {
            if(state == ALLOCSTATE_NEW)
            {
                *pfIsBranched = true;
            }
            else
            {
                ret = RedImapBlockState(InodeBlock(ulInode, 1U), &state);

                if(ret == 0)
                {
                    if(state == ALLOCSTATE_NEW)
                    {
                        *pfIsBranched = true;
                    }
                    else
                    {
                        *pfIsBranched = false;
                    }
                }
            }
        }
    }

    return ret;
}


/** @brief Branch an inode.

    A branched inode is one in which the allocation state for one copy is free
    or almost free, and the other copy is in the new state.  The copy which is
    in the new state is the writeable copy, which is also buffered and dirty.

    @param pInode   Pointer to the cached inode structure which has already been
                    mounted.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EINVAL Invalid parameters.
    @retval -RED_EIO    A disk I/O error occurred.
*/
REDSTATUS RedInodeBranch(
    CINODE *pInode)
{
    REDSTATUS ret;

    if(!CINODE_IS_MOUNTED(pInode))
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
    else if(!pInode->fBranched)
    {
        uint8_t bWhich;

        ret = InodeGetWriteableCopy(pInode->ulInode, &bWhich);

        if(ret == 0)
        {
            RedBufferBranch(pInode->pInodeBuf, InodeBlock(pInode->ulInode, bWhich));
            pInode->fBranched = true;
            pInode->fDirty = true;
        }

        /*  Toggle the inode slots: the old slot block becomes almost free
            (still used by the committed state) and the new slot block becomes
            new.
        */
        if(ret == 0)
        {
            ret = InodeBitSet(pInode->ulInode, 1U - bWhich, false);
        }

        if(ret == 0)
        {
            ret = InodeBitSet(pInode->ulInode, bWhich, true);
        }

        CRITICAL_ASSERT(ret == 0);
    }
    else
    {
        RedBufferDirty(pInode->pInodeBuf);
        pInode->fDirty = true;
        ret = 0;
    }

    return ret;
}
#endif /* REDCONF_READ_ONLY == 0 */


#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1)
/** @brief Find a free inode number.

    @param pulInode On successful return, populated with a free inode number.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EINVAL @p pulInode is `NULL`.
    @retval -RED_EIO    A disk I/O error occurred.
    @retval -RED_ENFILE No available inode numbers.
*/
static REDSTATUS InodeFindFree(
    uint32_t   *pulInode)
{
    REDSTATUS   ret;

    if(pulInode == NULL)
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
    else if(gpRedMR->ulFreeInodes == 0U)
    {
        ret = -RED_ENFILE;
    }
    else
    {
        uint32_t ulInode;

        ret = 0;

        for(ulInode = INODE_FIRST_FREE; ulInode < (INODE_FIRST_VALID + gpRedVolConf->ulInodeCount); ulInode++)
        {
            bool fFree;

            ret = RedInodeIsFree(ulInode, &fFree);

            if((ret != 0) || fFree)
            {
                break;
            }
        }

        if(ret == 0)
        {
            if(ulInode < (INODE_FIRST_VALID + gpRedVolConf->ulInodeCount))
            {
                *pulInode = ulInode;
            }
            else
            {
                /*  If gpRedMR->ulFreeInodes > 0, we should have found an inode.
                */
                CRITICAL_ERROR();
                ret = -RED_ENFILE;
            }
        }
    }

    return ret;
}
#endif


#if ((REDCONF_READ_ONLY == 0) && ((REDCONF_API_POSIX == 1) || FORMAT_SUPPORTED)) || (REDCONF_CHECKER == 1)
/** @brief Determine whether an inode number is available.

    @param ulInode  The node number to examine.
    @param pfFree   On successful return, populated with whether the inode
                    number is available (true) or in use (false).

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EINVAL @p pfFree is `NULL`; or @p ulInode is not a valid inode
                        number.
    @retval -RED_EIO    A disk I/O error occurred.
*/
REDSTATUS RedInodeIsFree(
    uint32_t    ulInode,
    bool       *pfFree)
{
    REDSTATUS   ret;

    if(pfFree == NULL)
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
    else
    {
        bool fSlot0Allocated;

        *pfFree = false;

        ret = RedInodeBitGet(gpRedCoreVol->bCurMR, ulInode, 0U, &fSlot0Allocated);
        if((ret == 0) && !fSlot0Allocated)
        {
            bool fSlot1Allocated;

            ret = RedInodeBitGet(gpRedCoreVol->bCurMR, ulInode, 1U, &fSlot1Allocated);
            if((ret == 0) && !fSlot1Allocated)
            {
                *pfFree = true;
            }
        }
    }

    return ret;
}
#endif


#if REDCONF_READ_ONLY == 0
/** @brief Determine which copy of the inode is currently writeable.

    @param ulInode  The inode number to examine.
    @param pbWhich  On successful return, populated with which copy of the inode
                    (either 0 or 1) is writeable.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EINVAL @p pbWhich is `NULL`; or ulInode is not a valid inode
                        number.
    @retval -RED_EIO    A disk I/O error occurred.
*/
static REDSTATUS InodeGetWriteableCopy(
    uint32_t    ulInode,
    uint8_t    *pbWhich)
{
    REDSTATUS   ret;

    if(pbWhich == NULL)
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
    else
    {
        bool fSlot0Allocated;

        /*  The writeable inode slot is the one which is free in the committed
            state, so query the committed state metaroot.
        */
        ret = RedInodeBitGet(1U - gpRedCoreVol->bCurMR, ulInode, 0U, &fSlot0Allocated);

        if(ret == 0)
        {
            if(!fSlot0Allocated)
            {
                *pbWhich = 0U;
            }
            else
            {
                bool fSlot1Allocated;

                ret = RedInodeBitGet(1U - gpRedCoreVol->bCurMR, ulInode, 1U, &fSlot1Allocated);

                if(ret == 0)
                {
                    if(!fSlot1Allocated)
                    {
                        *pbWhich = 1U;
                    }
                    else
                    {
                        /*  Both inode slots were allocated, which should never
                            happen.
                        */
                        CRITICAL_ERROR();
                        ret = -RED_EFUBAR;
                    }
                }
            }
        }
    }

    return ret;
}
#endif


/** @brief Determine which copy of the inode is current.

    @param ulInode  The inode number to examine.
    @param pbWhich  On successful return, populated with which copy of the inode
                    (either 0 or 1) is current.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EBADF  @p ulInode is an unallocated inode number.
    @retval -RED_EINVAL @p pbWhich is `NULL`; or ulInode is not a valid inode
                        number.
    @retval -RED_EIO    A disk I/O error occurred.
*/
static REDSTATUS InodeGetCurrentCopy(
    uint32_t    ulInode,
    uint8_t    *pbWhich)
{
    REDSTATUS   ret;

    if(pbWhich == NULL)
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
    else
    {
        bool fSlot0Allocated;

        /*  The current inode slot is the one which is allocated in the working
            state metaroot.
        */
        ret = RedInodeBitGet(gpRedCoreVol->bCurMR, ulInode, 0U, &fSlot0Allocated);
        if(ret == 0)
        {
            if(fSlot0Allocated)
            {
                *pbWhich = 0U;
            }
            else
            {
                bool fSlot1Allocated;

                ret = RedInodeBitGet(gpRedCoreVol->bCurMR, ulInode, 1U, &fSlot1Allocated);
                if(ret == 0)
                {
                    if(fSlot1Allocated)
                    {
                        *pbWhich = 1U;
                    }
                    else
                    {
                        /*  Neither slot for this inode was allocated, so the
                            inode is actually free.
                        */
                        ret = -RED_EBADF;
                    }
                }
            }
        }
    }

    return ret;
}


/** @brief Get whether a copy of an inode is allocated.

    @param bMR          The metaroot index: either 0 or 1.
    @param ulInode      The inode number.
    @param bWhich       Which copy of the inode to get.
    @param pfAllocated  On successful return, populated with whether the given
                        copy of the inode is allocated.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EINVAL @p bMR is not 1 or 0; @p ulInode is not a valid inode
                        number; or @p bWhich is not 1 or 0; or @p pfAllocated is
                        `NULL`.
    @retval -RED_EIO    A disk I/O error occurred.
*/
REDSTATUS RedInodeBitGet(
    uint8_t     bMR,
    uint32_t    ulInode,
    uint8_t     bWhich,
    bool       *pfAllocated)
{
    REDSTATUS   ret;

    if(!INODE_IS_VALID(ulInode) || (bWhich > 1U))
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
    else
    {
        ret = RedImapBlockGet(bMR, InodeBlock(ulInode, bWhich), pfAllocated);
    }

    return ret;
}


#if REDCONF_READ_ONLY == 0
/** @brief Set whether a copy of an inode is allocated.

    @param ulInode      The inode number.
    @param bWhich       Which copy of the inode to set.
    @param fAllocated   If true, the inode is set to allocated; if false, the
                        inode is set to free.

    @return A negated ::REDSTATUS code indicating the operation result.

    @retval 0           Operation was successful.
    @retval -RED_EINVAL @p ulInode is not a valid inode number; or @p bWhich is
                        not 1 or 0.
    @retval -RED_EIO    A disk I/O error occurred.
*/
static REDSTATUS InodeBitSet(
    uint32_t    ulInode,
    uint8_t     bWhich,
    bool        fAllocated)
{
    REDSTATUS   ret;

    if(!INODE_IS_VALID(ulInode) || (bWhich > 1U))
    {
        REDERROR();
        ret = -RED_EINVAL;
    }
    else
    {
        ret = RedImapBlockSet(InodeBlock(ulInode, bWhich), fAllocated);
    }

    return ret;
}
#endif


/** @brief Determine the block number of an inode.

    @param ulInode  The inode number.
    @param bWhich   Which copy of the inode.

    @return The block number of the inode.
*/
static uint32_t InodeBlock(
    uint32_t    ulInode,
    uint8_t     bWhich)
{
    REDASSERT(INODE_IS_VALID(ulInode));
    REDASSERT(bWhich <= 1U);

    return gpRedCoreVol->ulInodeTableStartBN + ((ulInode - INODE_FIRST_VALID) * 2U) + bWhich;
}

