/*             ----> 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 cached 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 /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_INODE_TIMESTAMPS == 1 ) */
                ( void ) bTimeFields;
            #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_INODE_TIMESTAMPS == 1 ) */

            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 DINDIR_POINTERS > 0U */


#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 /* if REDCONF_DIRECT_POINTERS < INODE_ENTRIES */


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


#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 ) && ( ( REDCONF_API_POSIX == 1 ) || FORMAT_SUPPORTED ) ) || ( REDCONF_CHECKER == 1 ) */


#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 /* if REDCONF_READ_ONLY == 0 */


/** @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 /* if REDCONF_READ_ONLY == 0 */


/** @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;
}
