/*             ----> 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 the entry-points to the core file system.
 */
#include <redfs.h>
#include <redcoreapi.h>
#include <redcore.h>


/*  Minimum number of blocks needed for metadata on any volume: the master
 *  block (1), the two metaroots (2), and one doubly-allocated inode (2),
 *  resulting in 1 + 2 + 2 = 5.
 */
#define MINIMUM_METADATA_BLOCKS    ( 5U )


#if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX == 1 )
    static REDSTATUS CoreCreate( uint32_t ulPInode,
                                 const char * pszName,
                                 bool fDir,
                                 uint32_t * pulInode );
#endif
#if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX == 1 ) && ( REDCONF_API_POSIX_LINK == 1 )
    static REDSTATUS CoreLink( uint32_t ulPInode,
                               const char * pszName,
                               uint32_t ulInode );
#endif
#if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX == 1 ) && ( ( REDCONF_API_POSIX_UNLINK == 1 ) || ( REDCONF_API_POSIX_RMDIR == 1 ) )
    static REDSTATUS CoreUnlink( uint32_t ulPInode,
                                 const char * pszName );
#endif
#if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX == 1 ) && ( REDCONF_API_POSIX_RENAME == 1 )
    static REDSTATUS CoreRename( uint32_t ulSrcPInode,
                                 const char * pszSrcName,
                                 uint32_t ulDstPInode,
                                 const char * pszDstName );
#endif
#if REDCONF_READ_ONLY == 0
    static REDSTATUS CoreFileWrite( uint32_t ulInode,
                                    uint64_t ullStart,
                                    uint32_t * pulLen,
                                    const void * pBuffer );
#endif
#if TRUNCATE_SUPPORTED
    static REDSTATUS CoreFileTruncate( uint32_t ulInode,
                                       uint64_t ullSize );
#endif


VOLUME gaRedVolume[ REDCONF_VOLUME_COUNT ];
static COREVOLUME gaCoreVol[ REDCONF_VOLUME_COUNT ];

const VOLCONF * CONST_IF_ONE_VOLUME gpRedVolConf = &gaRedVolConf[ 0U ];
VOLUME * CONST_IF_ONE_VOLUME gpRedVolume = &gaRedVolume[ 0U ];
COREVOLUME * CONST_IF_ONE_VOLUME gpRedCoreVol = &gaCoreVol[ 0U ];
METAROOT * gpRedMR = &gaCoreVol[ 0U ].aMR[ 0U ];

CONST_IF_ONE_VOLUME uint8_t gbRedVolNum = 0;


/** @brief Initialize the Reliance Edge file system driver.
 *
 *  Prepares the Reliance Edge file system driver to be used.  Must be the first
 *  Reliance Edge function to be invoked: no volumes can be mounted until the
 *  driver has been initialized.
 *
 *  If this function is called when the Reliance Edge driver is already
 *  initialized, the behavior is undefined.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0   Operation was successful.
 */
REDSTATUS RedCoreInit( void )
{
    REDSTATUS ret = 0;
    uint8_t bVolNum;

    #if REDCONF_OUTPUT == 1
        static uint8_t bSignedOn = 0U; /* Whether the sign on has been printed. */

        if( bSignedOn == 0U )
        {
            RedSignOn();
            bSignedOn = 1U;
        }
    #else

        /*  Call RedSignOn() even when output is disabled, to force the copyright
         *  text to be referenced and pulled into the program data.
         */
        RedSignOn();
    #endif

    RedMemSet( gaRedVolume, 0U, sizeof( gaRedVolume ) );
    RedMemSet( gaCoreVol, 0U, sizeof( gaCoreVol ) );

    RedBufferInit();

    for( bVolNum = 0U; bVolNum < REDCONF_VOLUME_COUNT; bVolNum++ )
    {
        VOLUME * pVol = &gaRedVolume[ bVolNum ];
        COREVOLUME * pCoreVol = &gaCoreVol[ bVolNum ];
        const VOLCONF * pVolConf = &gaRedVolConf[ bVolNum ];

        if( ( pVolConf->ulSectorSize < SECTOR_SIZE_MIN ) ||
            ( ( REDCONF_BLOCK_SIZE % pVolConf->ulSectorSize ) != 0U ) ||
            ( pVolConf->ulInodeCount == 0U ) )
        {
            ret = -RED_EINVAL;
        }

        #if REDCONF_API_POSIX == 1
            else if( pVolConf->pszPathPrefix == NULL )
            {
                ret = -RED_EINVAL;
            }
            else
            {
                #if REDCONF_VOLUME_COUNT > 1U
                    uint8_t bCmpVol;

                    /*  Ensure there are no duplicate path prefixes.  Check against all
                     *  previous volumes, which are already verified.
                     */
                    for( bCmpVol = 0U; bCmpVol < bVolNum; bCmpVol++ )
                    {
                        const char * pszCmpPathPrefix = gaRedVolConf[ bCmpVol ].pszPathPrefix;

                        if( RedStrCmp( pVolConf->pszPathPrefix, pszCmpPathPrefix ) == 0 )
                        {
                            ret = -RED_EINVAL;
                            break;
                        }
                    }
                #endif /* if REDCONF_VOLUME_COUNT > 1U */
            }
        #endif /* if REDCONF_API_POSIX == 1 */

        if( ret == 0 )
        {
            pVol->bBlockSectorShift = 0U;

            while( ( pVolConf->ulSectorSize << pVol->bBlockSectorShift ) < REDCONF_BLOCK_SIZE )
            {
                pVol->bBlockSectorShift++;
            }

            /*  This should always be true since the block size is confirmed to
             *  be a power of two (checked at compile time) and above we ensured
             *  that (REDCONF_BLOCK_SIZE % pVolConf->ulSectorSize) == 0.
             */
            REDASSERT( ( pVolConf->ulSectorSize << pVol->bBlockSectorShift ) == REDCONF_BLOCK_SIZE );

            pVol->ulBlockCount = ( uint32_t ) ( pVolConf->ullSectorCount >> pVol->bBlockSectorShift );

            if( pVol->ulBlockCount < MINIMUM_METADATA_BLOCKS )
            {
                ret = -RED_EINVAL;
            }
            else
            {
                #if REDCONF_READ_ONLY == 0
                    pVol->ulTransMask = REDCONF_TRANSACT_DEFAULT;
                #endif

                pVol->ullMaxInodeSize = INODE_SIZE_MAX;

                /*  To understand the following code, note that the fixed-
                 *  location metadata is located at the start of the disk, in
                 *  the following order:
                 *
                 *  - Master block (1 block)
                 *  - Metaroots (2 blocks)
                 *  - External imap blocks (variable * 2 blocks)
                 *  - Inode blocks (pVolConf->ulInodeCount * 2 blocks)
                 */

                /*  The imap needs bits for all inode and allocable blocks.  If
                 *  that bitmap will fit into the metaroot, the inline imap is
                 *  used and there are no imap nodes on disk.  The minus 3 is
                 *  there since the imap does not include bits for the master
                 *  block or metaroots.
                 */
                pCoreVol->fImapInline = ( pVol->ulBlockCount - 3U ) <= METAROOT_ENTRIES;

                if( pCoreVol->fImapInline )
                {
                    #if REDCONF_IMAP_INLINE == 1
                        pCoreVol->ulInodeTableStartBN = 3U;
                    #else
                        ret = -RED_EINVAL;
                    #endif
                }
                else
                {
                    #if REDCONF_IMAP_EXTERNAL == 1
                        pCoreVol->ulImapStartBN = 3U;

                        /*  The imap does not include bits for itself, so add two to
                         *  the number of imap entries for the two blocks of each
                         *  imap node.  This allows us to divide up the remaining
                         *  space, making sure to round up so all data blocks are
                         *  covered.
                         */
                        pCoreVol->ulImapNodeCount =
                            ( ( pVol->ulBlockCount - 3U ) + ( ( IMAPNODE_ENTRIES + 2U ) - 1U ) ) / ( IMAPNODE_ENTRIES + 2U );

                        pCoreVol->ulInodeTableStartBN = pCoreVol->ulImapStartBN + ( pCoreVol->ulImapNodeCount * 2U );
                    #else
                        ret = -RED_EINVAL;
                    #endif
                }
            }
        }

        if( ret == 0 )
        {
            pCoreVol->ulFirstAllocableBN = pCoreVol->ulInodeTableStartBN + ( pVolConf->ulInodeCount * 2U );

            if( pCoreVol->ulFirstAllocableBN > pVol->ulBlockCount )
            {
                /*  We can get here if there is not enough space for the number
                 *  of configured inodes.
                 */
                ret = -RED_EINVAL;
            }
            else
            {
                pVol->ulBlocksAllocable = pVol->ulBlockCount - pCoreVol->ulFirstAllocableBN;
            }
        }

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

    /*  Make sure the configured endianness is correct.
     */
    if( ret == 0 )
    {
        uint16_t uValue = 0xFF00U;
        uint8_t abBytes[ 2U ];

        RedMemCpy( abBytes, &uValue, sizeof( abBytes ) );

        #if REDCONF_ENDIAN_BIG == 1
            if( abBytes[ 0U ] != 0xFFU )
        #else
            if( abBytes[ 0U ] != 0x00U )
        #endif
        {
            ret = -RED_EINVAL;
        }
    }

    if( ret == 0 )
    {
        ret = RedOsClockInit();

        #if REDCONF_TASK_COUNT > 1U
            if( ret == 0 )
            {
                ret = RedOsMutexInit();

                if( ret != 0 )
                {
                    ( void ) RedOsClockUninit();
                }
            }
        #endif
    }

    return ret;
}


/** @brief Uninitialize the Reliance Edge file system driver.
 *
 *  Tears down the Reliance Edge file system driver.  Cannot be used until all
 *  Reliance Edge volumes are unmounted.  A subsequent call to RedCoreInit()
 *  will initialize the driver again.
 *
 *  The behavior of calling this function when the core is already uninitialized
 *  is undefined.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBUSY  At least one volume is still mounted.
 */
REDSTATUS RedCoreUninit( void )
{
    REDSTATUS ret;

    #if REDCONF_TASK_COUNT > 1U
        ret = RedOsMutexUninit();

        if( ret == 0 )
    #endif
    {
        ret = RedOsClockUninit();
    }

    return ret;
}


/** @brief Set the current volume.
 *
 *  All core APIs operate on the current volume.  This call must precede all
 *  core accesses.
 *
 *  @param bVolNum  The volume number to access.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL @p bVolNum is an invalid volume number.
 */
REDSTATUS RedCoreVolSetCurrent( uint8_t bVolNum )
{
    REDSTATUS ret;

    if( bVolNum >= REDCONF_VOLUME_COUNT )
    {
        ret = -RED_EINVAL;
    }
    else
    {
        #if REDCONF_VOLUME_COUNT > 1U
            gbRedVolNum = bVolNum;
            gpRedVolConf = &gaRedVolConf[ bVolNum ];
            gpRedVolume = &gaRedVolume[ bVolNum ];
            gpRedCoreVol = &gaCoreVol[ bVolNum ];
            gpRedMR = &gpRedCoreVol->aMR[ gpRedCoreVol->bCurMR ];
        #endif

        ret = 0;
    }

    return ret;
}


#if FORMAT_SUPPORTED

/** @brief Format a file system volume.
 *
 *  Uses the statically defined volume configuration.  After calling this
 *  function, the volume needs to be mounted -- see RedCoreVolMount().
 *
 *  An error is returned if the volume is mounted.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBUSY  Volume is mounted.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    REDSTATUS RedCoreVolFormat( void )
    {
        return RedVolFormat();
    }
#endif /* FORMAT_SUPPORTED */


/** @brief Mount a file system volume.
 *
 *  Prepares the file system volume to be accessed.  Mount will fail if the
 *  volume has never been formatted, or if the on-disk format is inconsistent
 *  with the compile-time configuration.
 *
 *  If the volume is already mounted, the behavior is undefined.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    Volume not formatted, improperly formatted, or corrupt.
 */
REDSTATUS RedCoreVolMount( void )
{
    return RedVolMount();
}


/** @brief Unmount a file system volume.
 *
 *  This function discards the in-memory state for the file system and marks it
 *  as unmounted.  Subsequent attempts to access the volume will fail until the
 *  volume is mounted again.
 *
 *  If unmount automatic transaction points are enabled, this function will
 *  commit a transaction point prior to unmounting.  If unmount automatic
 *  transaction points are disabled, this function will unmount without
 *  transacting, effectively discarding the working state.
 *
 *  If the volume is already unmounted, the behavior is undefined.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EIO    I/O error during unmount automatic transaction point.
 */
REDSTATUS RedCoreVolUnmount( void )
{
    REDSTATUS ret = 0;

    #if REDCONF_READ_ONLY == 0
        if( !gpRedVolume->fReadOnly && ( ( gpRedVolume->ulTransMask & RED_TRANSACT_UMOUNT ) != 0U ) )
        {
            ret = RedVolTransact();
        }
    #endif

    if( ret == 0 )
    {
        ret = RedBufferDiscardRange( 0U, gpRedVolume->ulBlockCount );
    }

    if( ret == 0 )
    {
        ret = RedOsBDevClose( gbRedVolNum );
    }

    if( ret == 0 )
    {
        gpRedVolume->fMounted = false;
    }

    return ret;
}


#if REDCONF_READ_ONLY == 0

/** @brief Commit a transaction point.
 *
 *  Reliance Edge is a transactional file system.  All modifications, of both
 *  metadata and filedata, are initially working state.  A transaction point
 *  is a process whereby the working state atomically becomes the committed
 *  state, replacing the previous committed state.  Whenever Reliance Edge is
 *  mounted, including after power loss, the state of the file system after
 *  mount is the most recent committed state.  Nothing from the committed
 *  state is ever missing, and nothing from the working state is ever included.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL The volume is not mounted.
 *  @retval -RED_EIO    A disk I/O error occurred.
 *  @retval -RED_EROFS  The file system volume is read-only.
 */
    REDSTATUS RedCoreVolTransact( void )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted )
        {
            ret = -RED_EINVAL;
        }
        else if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            ret = RedVolTransact();
        }

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


#if REDCONF_API_POSIX == 1

/** @brief Query file system status information.
 *
 *  @param pStatFS  The buffer to populate with volume information.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval -RED_EINVAL Volume is not mounted; or @p pStatFS is `NULL`.
 */
    REDSTATUS RedCoreVolStat( REDSTATFS * pStatFS )
    {
        REDSTATUS ret;

        if( ( pStatFS == NULL ) || ( !gpRedVolume->fMounted ) )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            RedMemSet( pStatFS, 0U, sizeof( *pStatFS ) );

            pStatFS->f_bsize = REDCONF_BLOCK_SIZE;
            pStatFS->f_frsize = REDCONF_BLOCK_SIZE;
            pStatFS->f_blocks = gpRedVolume->ulBlockCount;
            #if RESERVED_BLOCKS > 0U
                pStatFS->f_bfree = ( gpRedMR->ulFreeBlocks > RESERVED_BLOCKS ) ? ( gpRedMR->ulFreeBlocks - RESERVED_BLOCKS ) : 0U;
            #else
                pStatFS->f_bfree = gpRedMR->ulFreeBlocks;
            #endif
            pStatFS->f_bavail = pStatFS->f_bfree;
            pStatFS->f_files = gpRedVolConf->ulInodeCount;
            pStatFS->f_ffree = gpRedMR->ulFreeInodes;
            pStatFS->f_favail = gpRedMR->ulFreeInodes;

            pStatFS->f_flag = RED_ST_NOSUID;
            #if REDCONF_READ_ONLY == 0
                if( gpRedVolume->fReadOnly )
            #endif
            {
                pStatFS->f_flag |= RED_ST_RDONLY;
            }

            pStatFS->f_namemax = REDCONF_NAME_MAX;
            pStatFS->f_maxfsize = INODE_SIZE_MAX;
            pStatFS->f_dev = gbRedVolNum;

            ret = 0;
        }

        return ret;
    }
#endif /* REDCONF_API_POSIX == 1 */


#if ( REDCONF_READ_ONLY == 0 ) && ( ( REDCONF_API_POSIX == 1 ) || ( REDCONF_API_FSE_TRANSMASKSET == 1 ) )

/** @brief Update the transaction mask.
 *
 *  The following events are available when using the FSE API:
 *
 *  - #RED_TRANSACT_UMOUNT
 *  - #RED_TRANSACT_WRITE
 *  - #RED_TRANSACT_TRUNCATE
 *  - #RED_TRANSACT_VOLFULL
 *
 *  The following events are available when using the POSIX-like API:
 *
 *  - #RED_TRANSACT_UMOUNT
 *  - #RED_TRANSACT_CREAT
 *  - #RED_TRANSACT_UNLINK
 *  - #RED_TRANSACT_MKDIR
 *  - #RED_TRANSACT_RENAME
 *  - #RED_TRANSACT_LINK
 *  - #RED_TRANSACT_CLOSE
 *  - #RED_TRANSACT_WRITE
 *  - #RED_TRANSACT_FSYNC
 *  - #RED_TRANSACT_TRUNCATE
 *  - #RED_TRANSACT_VOLFULL
 *
 *  The #RED_TRANSACT_MANUAL macro (by itself) may be used to disable all
 *  automatic transaction events.  The #RED_TRANSACT_MASK macro is a bitmask of
 *  all transaction flags, excluding those representing excluded functionality.
 *
 *  Attempting to enable events for excluded functionality will result in an
 *  error.
 *
 *  @param ulEventMask  A bitwise-OR'd mask of automatic transaction events to
 *                      be set as the current transaction mode.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL The volume is not mounted; or @p ulEventMask contains
 *                      invalid bits.
 *  @retval -RED_EROFS  The file system volume is read-only.
 */
    REDSTATUS RedCoreTransMaskSet( uint32_t ulEventMask )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted || ( ( ulEventMask & RED_TRANSACT_MASK ) != ulEventMask ) )
        {
            ret = -RED_EINVAL;
        }
        else if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            gpRedVolume->ulTransMask = ulEventMask;
            ret = 0;
        }

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


#if ( REDCONF_API_POSIX == 1 ) || ( REDCONF_API_FSE_TRANSMASKGET == 1 )

/** @brief Read the transaction mask.
 *
 *  If the volume is read-only, the returned event mask is always zero.
 *
 *  @param pulEventMask Populated with a bitwise-OR'd mask of automatic
 *                      transaction events which represent the current
 *                      transaction mode for the volume.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL The volume is not mounted; or @p pulEventMask is `NULL`.
 */
    REDSTATUS RedCoreTransMaskGet( uint32_t * pulEventMask )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted || ( pulEventMask == NULL ) )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            #if REDCONF_READ_ONLY == 1
                *pulEventMask = 0U;
            #else
                *pulEventMask = gpRedVolume->ulTransMask;
            #endif
            ret = 0;
        }

        return ret;
    }
#endif /* if ( REDCONF_API_POSIX == 1 ) || ( REDCONF_API_FSE_TRANSMASKGET == 1 ) */


#if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX == 1 )

/** @brief Create a file or directory.
 *
 *  @param ulPInode The inode number of the parent directory.
 *  @param pszName  A null-terminated name for the new inode.
 *  @param fDir     Whether to create a directory (true) or file (false).
 *  @param pulInode On successful return, populated with the inode number of the
 *                  new file or directory.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0                   Operation was successful.
 *  @retval -RED_EINVAL         The volume is not mounted; or @p pszName is not
 *                              a valid name; or @p pulInode is `NULL`.
 *  @retval -RED_EIO            A disk I/O error occurred.
 *  @retval -RED_EROFS          The file system volume is read-only.
 *  @retval -RED_ENOTDIR        @p ulPInode is not a directory.
 *  @retval -RED_EBADF          @p ulPInode is not a valid inode.
 *  @retval -RED_ENOSPC         There is not enough space on the volume to
 *                              createthe new directory entry; or the directory
 *                              is full.
 *  @retval -RED_ENFILE         No available inode slots.
 *  @retval -RED_ENAMETOOLONG   @p pszName is too long.
 *  @retval -RED_EEXIST         @p pszName already exists in @p ulPInode.
 */
    REDSTATUS RedCoreCreate( uint32_t ulPInode,
                             const char * pszName,
                             bool fDir,
                             uint32_t * pulInode )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted )
        {
            ret = -RED_EINVAL;
        }
        else if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            ret = CoreCreate( ulPInode, pszName, fDir, pulInode );

            if( ( ret == -RED_ENOSPC ) &&
                ( ( gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL ) != 0U ) &&
                ( gpRedCoreVol->ulAlmostFreeBlocks > 0U ) )
            {
                ret = RedVolTransact();

                if( ret == 0 )
                {
                    ret = CoreCreate( ulPInode, pszName, fDir, pulInode );
                }
            }

            if( ret == 0 )
            {
                if( fDir && ( ( gpRedVolume->ulTransMask & RED_TRANSACT_MKDIR ) != 0U ) )
                {
                    ret = RedVolTransact();
                }
                else if( !fDir && ( ( gpRedVolume->ulTransMask & RED_TRANSACT_CREAT ) != 0U ) )
                {
                    ret = RedVolTransact();
                }
                else
                {
                    /*  No automatic transaction for this operation.
                     */
                }
            }
        }

        return ret;
    }


/** @brief Create a file or directory.
 *
 *  @param ulPInode The inode number of the parent directory.
 *  @param pszName  A null-terminated name for the new inode.
 *  @param fDir     Whether to create a directory (true) or file (false).
 *  @param pulInode On successful return, populated with the inode number of the
 *                  new file or directory.
 *
 *  @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_EROFS          The file system volume is read-only.
 *  @retval -RED_ENOTDIR        @p ulPInode is not a directory.
 *  @retval -RED_EBADF          @p ulPInode is not a valid inode.
 *  @retval -RED_ENOSPC         There is not enough space on the volume to
 *                              create the new directory entry; or the directory
 *                              is full.
 *  @retval -RED_ENFILE         No available inode slots.
 *  @retval -RED_ENAMETOOLONG   @p pszName is too long.
 *  @retval -RED_EEXIST         @p pszName already exists in @p ulPInode.
 */
    static REDSTATUS CoreCreate( uint32_t ulPInode,
                                 const char * pszName,
                                 bool fDir,
                                 uint32_t * pulInode )
    {
        REDSTATUS ret;

        if( pulInode == NULL )
        {
            ret = -RED_EINVAL;
        }
        else if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            CINODE pino;

            pino.ulInode = ulPInode;
            ret = RedInodeMount( &pino, FTYPE_DIR, false );

            if( ret == 0 )
            {
                CINODE ino;

                ino.ulInode = INODE_INVALID;
                ret = RedInodeCreate( &ino, ulPInode, fDir ? RED_S_IFDIR : RED_S_IFREG );

                if( ret == 0 )
                {
                    ret = RedInodeBranch( &pino );

                    if( ret == 0 )
                    {
                        ret = RedDirEntryCreate( &pino, pszName, ino.ulInode );
                    }

                    if( ret == 0 )
                    {
                        *pulInode = ino.ulInode;
                    }
                    else
                    {
                        REDSTATUS ret2;

                        ret2 = RedInodeFree( &ino );
                        CRITICAL_ASSERT( ret2 == 0 );
                    }

                    RedInodePut( &ino, 0U );
                }

                RedInodePut( &pino, ( ret == 0 ) ? ( uint8_t ) ( IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME ) : 0U );
            }
        }

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


#if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX == 1 ) && ( REDCONF_API_POSIX_LINK == 1 )

/** @brief Create a hard link.
 *
 *  This creates an additional name (link) for @p ulInode.  The new name refers
 *  to the same file with the same contents.  If a name is deleted, but the
 *  underlying file has other names, the file continues to exist.  The link
 *  count (accessible via RedCoreStat()) indicates the number of names that a
 *  file has.  All of a file's names are on equal footing: there is nothing
 *  special about the original name.
 *
 *  If @p ulInode names a directory, the operation will fail.
 *
 *  @param ulPInode The inode number of the parent directory.
 *  @param pszName  The null-terminated name for the new link.
 *  @param ulInode  The inode to create a hard link to.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0                   Operation was successful.
 *  @retval -RED_EBADF          @p ulPInode is not a valid inode; or @p ulInode
 *                              is not a valid inode.
 *  @retval -RED_EEXIST         @p pszName resolves to an existing file.
 *  @retval -RED_EINVAL         The volume is not mounted; or @p pszName is
 *                              `NULL`.
 *  @retval -RED_EIO            A disk I/O error occurred.
 *  @retval -RED_EMLINK         Creating the link would exceed the maximum link
 *                              count of @p ulInode.
 *  @retval -RED_ENAMETOOLONG   Attempting to create a link with a name that
 *                              exceeds the maximum name length.
 *  @retval -RED_ENOSPC         There is insufficient free space to expand the
 *                              directory that would contain the link.
 *  @retval -RED_ENOTDIR        @p ulPInode is not a directory.
 *  @retval -RED_EPERM          @p ulInode is a directory.
 *  @retval -RED_EROFS          The requested link requires writing in a
 *                              directory on a read-only file system.
 */
    REDSTATUS RedCoreLink( uint32_t ulPInode,
                           const char * pszName,
                           uint32_t ulInode )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted )
        {
            ret = -RED_EINVAL;
        }
        else if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            ret = CoreLink( ulPInode, pszName, ulInode );

            if( ( ret == -RED_ENOSPC ) &&
                ( ( gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL ) != 0U ) &&
                ( gpRedCoreVol->ulAlmostFreeBlocks > 0U ) )
            {
                ret = RedVolTransact();

                if( ret == 0 )
                {
                    ret = CoreLink( ulPInode, pszName, ulInode );
                }
            }

            if( ( ret == 0 ) && ( ( gpRedVolume->ulTransMask & RED_TRANSACT_LINK ) != 0U ) )
            {
                ret = RedVolTransact();
            }
        }

        return ret;
    }


/** @brief Create a hard link.
 *
 *  @param ulPInode The inode number of the parent directory.
 *  @param pszName  The null-terminated name for the new link.
 *  @param ulInode  The inode to create a hard link to.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0                   Operation was successful.
 *  @retval -RED_EBADF          @p ulPInode is not a valid inode; or @p ulInode
 *                              is not a valid inode.
 *  @retval -RED_EEXIST         @p pszName resolves to an existing file.
 *  @retval -RED_EIO            A disk I/O error occurred.
 *  @retval -RED_EMLINK         Creating the link would exceed the maximum link
 *                              count of @p ulInode.
 *  @retval -RED_ENAMETOOLONG   Attempting to create a link with a name that
 *                              exceeds the maximum name length.
 *  @retval -RED_ENOSPC         There is insufficient free space to expand the
 *                              directory that would contain the link.
 *  @retval -RED_ENOTDIR        @p ulPInode is not a directory.
 *  @retval -RED_EPERM          @p ulInode is a directory.
 *  @retval -RED_EROFS          The requested link requires writing in a
 *                              directory on a read-only file system.
 */
    static REDSTATUS CoreLink( uint32_t ulPInode,
                               const char * pszName,
                               uint32_t ulInode )
    {
        REDSTATUS ret;

        if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            CINODE pino;

            pino.ulInode = ulPInode;
            ret = RedInodeMount( &pino, FTYPE_DIR, false );

            if( ret == 0 )
            {
                CINODE ino;

                ino.ulInode = ulInode;
                ret = RedInodeMount( &ino, FTYPE_FILE, false );

                /*  POSIX specifies EPERM as the errno thrown when link() is given a
                 *  directory.  Switch the errno returned if EISDIR was the return
                 *  value.
                 */
                if( ret == -RED_EISDIR )
                {
                    ret = -RED_EPERM;
                }

                if( ret == 0 )
                {
                    if( ino.pInodeBuf->uNLink == UINT16_MAX )
                    {
                        ret = -RED_EMLINK;
                    }
                    else
                    {
                        ret = RedInodeBranch( &pino );
                    }

                    if( ret == 0 )
                    {
                        ret = RedInodeBranch( &ino );
                    }

                    if( ret == 0 )
                    {
                        ret = RedDirEntryCreate( &pino, pszName, ino.ulInode );
                    }

                    if( ret == 0 )
                    {
                        ino.pInodeBuf->uNLink++;
                    }

                    RedInodePut( &ino, ( ret == 0 ) ? IPUT_UPDATE_CTIME : 0U );
                }

                RedInodePut( &pino, ( ret == 0 ) ? ( uint8_t ) ( IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME ) : 0U );
            }
        }

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


#if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX == 1 ) && ( ( REDCONF_API_POSIX_UNLINK == 1 ) || ( REDCONF_API_POSIX_RMDIR == 1 ) )

/** @brief Delete a file or directory.
 *
 *  The given name is deleted and the link count of the corresponding inode is
 *  decremented.  If the link count falls to zero (no remaining hard links),
 *  the inode will be deleted.
 *
 *  If the path names a directory which is not empty, the unlink will fail.
 *
 *  If the deletion frees data in the committed state, it will not return to
 *  free space until after a transaction point.  Similarly, if the inode was
 *  part of the committed state, the inode slot will not be available until
 *  after a transaction point.
 *
 *  This function can fail when the disk is full.  To fix this, transact and
 *  try again: Reliance Edge guarantees that it is possible to delete at least
 *  one file or directory after a transaction point.  If disk full automatic
 *  transactions are enabled, this will happen automatically.
 *
 *  @param ulPInode The inode number of the parent directory.
 *  @param pszName  The null-terminated name of the file or directory to
 *                  delete.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0                   Operation was successful.
 *  @retval -RED_EBADF          @p ulPInode is not a valid inode.
 *  @retval -RED_EINVAL         The volume is not mounted; or @p pszName is
 *                              `NULL`.
 *  @retval -RED_EIO            A disk I/O error occurred.
 *  @retval -RED_ENAMETOOLONG   @p pszName is too long.
 *  @retval -RED_ENOENT         @p pszName does not name an existing file or
 *                              directory.
 *  @retval -RED_ENOTDIR        @p ulPInode is not a directory.
 *  @retval -RED_ENOSPC         The file system does not have enough space to
 *                              modify the parent directory to perform the
 *                              deletion.
 *  @retval -RED_ENOTEMPTY      The inode referred to by @p pszName is a
 *                              directory which is not empty.
 */
    REDSTATUS RedCoreUnlink( uint32_t ulPInode,
                             const char * pszName )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted )
        {
            ret = -RED_EINVAL;
        }
        else if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            ret = CoreUnlink( ulPInode, pszName );

            if( ( ret == -RED_ENOSPC ) &&
                ( ( gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL ) != 0U ) &&
                ( gpRedCoreVol->ulAlmostFreeBlocks > 0U ) )
            {
                ret = RedVolTransact();

                if( ret == 0 )
                {
                    ret = CoreUnlink( ulPInode, pszName );
                }
            }

            if( ( ret == 0 ) && ( ( gpRedVolume->ulTransMask & RED_TRANSACT_UNLINK ) != 0U ) )
            {
                ret = RedVolTransact();
            }
        }

        return ret;
    }


/** @brief Delete a file or directory.
 *
 *  @param ulPInode The inode number of the parent directory.
 *  @param pszName  The null-terminated name of the file or directory to
 *                  delete.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0                   Operation was successful.
 *  @retval -RED_EBADF          @p ulPInode is not a valid inode.
 *  @retval -RED_EIO            A disk I/O error occurred.
 *  @retval -RED_ENAMETOOLONG   @p pszName is too long.
 *  @retval -RED_ENOENT         @p pszName does not name an existing file or
 *                              directory.
 *  @retval -RED_ENOTDIR        @p ulPInode is not a directory.
 *  @retval -RED_ENOSPC         The file system does not have enough space to
 *                              modify the parent directory to perform the
 *                              deletion.
 *  @retval -RED_ENOTEMPTY      The inode referred to by @p pszName is a
 *                              directory which is not empty.
 */
    static REDSTATUS CoreUnlink( uint32_t ulPInode,
                                 const char * pszName )
    {
        REDSTATUS ret;

        if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            CINODE pino;

            pino.ulInode = ulPInode;
            ret = RedInodeMount( &pino, FTYPE_DIR, false );

            if( ret == 0 )
            {
                uint32_t ulDeleteIdx;
                uint32_t ulInode;

                ret = RedDirEntryLookup( &pino, pszName, &ulDeleteIdx, &ulInode );

                if( ret == 0 )
                {
                    ret = RedInodeBranch( &pino );
                }

                if( ret == 0 )
                {
                    CINODE ino;

                    ino.ulInode = ulInode;
                    ret = RedInodeMount( &ino, FTYPE_EITHER, false );

                    if( ret == 0 )
                    {
                        if( ino.fDirectory && ( ino.pInodeBuf->ullSize > 0U ) )
                        {
                            ret = -RED_ENOTEMPTY;
                        }
                        else
                        {
                            #if RESERVED_BLOCKS > 0U
                                gpRedCoreVol->fUseReservedBlocks = true;
                            #endif

                            ret = RedDirEntryDelete( &pino, ulDeleteIdx );

                            #if RESERVED_BLOCKS > 0U
                                gpRedCoreVol->fUseReservedBlocks = false;
                            #endif

                            if( ret == 0 )
                            {
                                /*  If the inode is deleted, buffers are needed to
                                 *  read all of the indirects and free the data
                                 *  blocks.  Before doing that, to reduce the
                                 *  minimum number of buffers needed to complete the
                                 *  unlink, release the parent directory inode
                                 *  buffers which are no longer needed.
                                 */
                                RedInodePutCoord( &pino );

                                ret = RedInodeLinkDec( &ino );
                                CRITICAL_ASSERT( ret == 0 );
                            }
                        }

                        RedInodePut( &ino, ( ret == 0 ) ? IPUT_UPDATE_CTIME : 0U );
                    }
                }

                RedInodePut( &pino, ( ret == 0 ) ? ( uint8_t ) ( IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME ) : 0U );
            }
        }

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


#if REDCONF_API_POSIX == 1

/** @brief Look up the inode number of a file or directory.
 *
 *  @param ulPInode The inode number of the parent directory.
 *  @param pszName  The null-terminated name of the file or directory to look
 *                  up.
 *  @param pulInode On successful return, populated with the inode number named
 *                  by @p pszName.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0               Operation was successful.
 *  @retval -RED_EBADF      @p ulPInode is not a valid inode.
 *  @retval -RED_EINVAL     The volume is not mounted; @p pszName is `NULL`; or
 *                          @p pulInode is `NULL`.
 *  @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 ulPInode is not a directory.
 */
    REDSTATUS RedCoreLookup( uint32_t ulPInode,
                             const char * pszName,
                             uint32_t * pulInode )
    {
        REDSTATUS ret;

        if( ( pulInode == NULL ) || !gpRedVolume->fMounted )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            CINODE ino;

            ino.ulInode = ulPInode;
            ret = RedInodeMount( &ino, FTYPE_DIR, false );

            if( ret == 0 )
            {
                ret = RedDirEntryLookup( &ino, pszName, NULL, pulInode );

                RedInodePut( &ino, 0U );
            }
        }

        return ret;
    }
#endif /* REDCONF_API_POSIX == 1 */


#if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX == 1 ) && ( REDCONF_API_POSIX_RENAME == 1 )

/** @brief Rename a file or directory.
 *
 *  If @p pszDstName names an existing file or directory, the behavior depends
 *  on the configuration.  If #REDCONF_RENAME_ATOMIC is false, and if the
 *  destination name exists, this function always fails with -RED_EEXIST.
 *
 *  If #REDCONF_RENAME_ATOMIC is true, and if the new name exists, then in one
 *  atomic operation, the destination name is unlinked and the source name is
 *  renamed to the destination name.  Both names must be of the same type (both
 *  files or both directories).  As with RedCoreUnlink(), if the destination
 *  name is a directory, it must be empty.  The major exception to this
 *  behavior is that if both names are links to the same inode, then the rename
 *  does nothing and both names continue to exist.
 *
 *  If the rename deletes the old destination, it may free data in the
 *  committed state, which will not return to free space until after a
 *  transaction point.  Similarly, if the deleted inode was part of the
 *  committed state, the inode slot will not be available until after a
 *  transaction point.
 *
 *  @param ulSrcPInode  The inode number of the parent directory of the file or
 *                      directory to rename.
 *  @param pszSrcName   The name of the file or directory to rename.
 *  @param ulDstPInode  The new parent directory inode number of the file or
 *                      directory after the rename.
 *  @param pszDstName   The new name of the file or directory after the rename.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0                   Operation was successful.
 *  @retval -RED_EBADF          @p ulSrcPInode is not a valid inode number; or
 *                              @p ulDstPInode is not a valid inode number.
 *  @retval -RED_EEXIST         #REDCONF_RENAME_POSIX is false and the
 *                              destination name exists.
 *  @retval -RED_EINVAL         The volume is not mounted; @p pszSrcName is
 *                              `NULL`; or @p pszDstName is `NULL`.
 *  @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 ulSrcPInode is not a directory; or
 *                              @p ulDstPInode 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 RedCoreRename( uint32_t ulSrcPInode,
                             const char * pszSrcName,
                             uint32_t ulDstPInode,
                             const char * pszDstName )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted )
        {
            ret = -RED_EINVAL;
        }
        else if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            ret = CoreRename( ulSrcPInode, pszSrcName, ulDstPInode, pszDstName );

            if( ( ret == -RED_ENOSPC ) &&
                ( ( gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL ) != 0U ) &&
                ( gpRedCoreVol->ulAlmostFreeBlocks > 0U ) )
            {
                ret = RedVolTransact();

                if( ret == 0 )
                {
                    ret = CoreRename( ulSrcPInode, pszSrcName, ulDstPInode, pszDstName );
                }
            }

            if( ( ret == 0 ) && ( ( gpRedVolume->ulTransMask & RED_TRANSACT_RENAME ) != 0U ) )
            {
                ret = RedVolTransact();
            }
        }

        return ret;
    }


/** @brief Rename a file or directory.
 *
 *  @param ulSrcPInode  The inode number of the parent directory of the file or
 *                      directory to rename.
 *  @param pszSrcName   The name of the file or directory to rename.
 *  @param ulDstPInode  The new parent directory inode number of the file or
 *                      directory after the rename.
 *  @param pszDstName   The new name of the file or directory after the rename.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0                   Operation was successful.
 *  @retval -RED_EBADF          @p ulSrcPInode is not a valid inode number; or
 *                              @p ulDstPInode is not a valid inode number.
 *  @retval -RED_EEXIST         #REDCONF_RENAME_POSIX is false and the
 *                              destination name exists.
 *  @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 ulSrcPInode is not a directory; or
 *                              @p ulDstPInode 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.
 */
    static REDSTATUS CoreRename( uint32_t ulSrcPInode,
                                 const char * pszSrcName,
                                 uint32_t ulDstPInode,
                                 const char * pszDstName )
    {
        REDSTATUS ret;

        if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            bool fUpdateTimestamps = false;
            CINODE SrcPInode;

            SrcPInode.ulInode = ulSrcPInode;
            ret = RedInodeMount( &SrcPInode, FTYPE_DIR, true );

            if( ret == 0 )
            {
                CINODE DstPInode;
                CINODE * pDstPInode;

                if( ulSrcPInode == ulDstPInode )
                {
                    pDstPInode = &SrcPInode;
                }
                else
                {
                    pDstPInode = &DstPInode;
                    DstPInode.ulInode = ulDstPInode;
                    ret = RedInodeMount( pDstPInode, FTYPE_DIR, true );
                }

                if( ret == 0 )
                {
                    /*  Initialize these to zero so we can unconditionally put them,
                     *  even if RedDirEntryRename() fails before mounting them.
                     */
                    CINODE SrcInode = { 0U };
                    CINODE DstInode = { 0U };

                    ret = RedDirEntryRename( &SrcPInode, pszSrcName, &SrcInode, pDstPInode, pszDstName, &DstInode );

                    #if REDCONF_RENAME_ATOMIC == 1
                        if( ( ret == 0 ) && ( DstInode.ulInode != INODE_INVALID ) && ( DstInode.ulInode != SrcInode.ulInode ) )
                        {
                            /*  If the inode is deleted, buffers are needed to read all
                             *  of the indirects and free the data blocks.  Before doing
                             *  that, to reduce the minimum number of buffers needed to
                             *  complete the rename, release parent directory inode
                             *  buffers which are no longer needed.
                             */
                            RedInodePutCoord( &SrcPInode );
                            RedInodePutCoord( pDstPInode );

                            ret = RedInodeLinkDec( &DstInode );
                            CRITICAL_ASSERT( ret == 0 );
                        }

                        if( ( ret == 0 ) && ( DstInode.ulInode != SrcInode.ulInode ) )
                    #else /* if REDCONF_RENAME_ATOMIC == 1 */
                        if( ret == 0 )
                    #endif /* if REDCONF_RENAME_ATOMIC == 1 */
                    {
                        fUpdateTimestamps = true;
                    }

                    #if REDCONF_RENAME_ATOMIC == 1
                        RedInodePut( &DstInode, 0U );
                    #endif

                    /*  POSIX says updating ctime for the source inode is optional,
                     *  but searching around it looks like this is common for Linux
                     *  and other Unix file systems.
                     */
                    RedInodePut( &SrcInode, fUpdateTimestamps ? IPUT_UPDATE_CTIME : 0U );
                    RedInodePut( pDstPInode, fUpdateTimestamps ? ( uint8_t ) ( IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME ) : 0U );
                }
            }

            RedInodePut( &SrcPInode, fUpdateTimestamps ? ( uint8_t ) ( IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME ) : 0U );
        }

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


#if REDCONF_API_POSIX == 1

/** @brief Get the status of a file or directory.
 *
 *  See the ::REDSTAT type for the details of the information returned.
 *
 *  @param ulInode  The inode number of the file or directory whose information
 *                  is to be retrieved.
 *  @param pStat    Pointer to a ::REDSTAT buffer to populate.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBADF  @p ulInode is not a valid inode.
 *  @retval -RED_EINVAL The volume is not mounted; @p pStat is `NULL`.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    REDSTATUS RedCoreStat( uint32_t ulInode,
                           REDSTAT * pStat )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted || ( pStat == NULL ) )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            CINODE ino;

            ino.ulInode = ulInode;
            ret = RedInodeMount( &ino, FTYPE_EITHER, false );

            if( ret == 0 )
            {
                RedMemSet( pStat, 0U, sizeof( *pStat ) );

                pStat->st_dev = gbRedVolNum;
                pStat->st_ino = ulInode;
                pStat->st_mode = ino.pInodeBuf->uMode;
                #if REDCONF_API_POSIX_LINK == 1
                    pStat->st_nlink = ino.pInodeBuf->uNLink;
                #else
                    pStat->st_nlink = 1U;
                #endif
                pStat->st_size = ino.pInodeBuf->ullSize;
                #if REDCONF_INODE_TIMESTAMPS == 1
                    pStat->st_atime = ino.pInodeBuf->ulATime;
                    pStat->st_mtime = ino.pInodeBuf->ulMTime;
                    pStat->st_ctime = ino.pInodeBuf->ulCTime;
                #endif
                #if REDCONF_INODE_BLOCKS == 1
                    pStat->st_blocks = ino.pInodeBuf->ulBlocks;
                #endif

                RedInodePut( &ino, 0U );
            }
        }

        return ret;
    }
#endif /* REDCONF_API_POSIX == 1 */


#if REDCONF_API_FSE == 1

/** @brief Get the size of a file.
 *
 *  @param ulInode  The inode number of the file whose size is to be retrieved.
 *  @param pullSize On successful exit, populated with the file size.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBADF  @p ulInode is not a valid inode.
 *  @retval -RED_EINVAL The volume is not mounted; @p pullSize is `NULL`.
 *  @retval -RED_EIO    A disk I/O error occurred.
 *  @retval -RED_EISDIR @p ulInode is a directory inode.
 */
    REDSTATUS RedCoreFileSizeGet( uint32_t ulInode,
                                  uint64_t * pullSize )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted || ( pullSize == NULL ) )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            CINODE ino;

            ino.ulInode = ulInode;
            ret = RedInodeMount( &ino, FTYPE_FILE, false );

            if( ret == 0 )
            {
                *pullSize = ino.pInodeBuf->ullSize;

                RedInodePut( &ino, 0U );
            }
        }

        return ret;
    }
#endif /* REDCONF_API_FSE == 1 */


/** @brief Read from a file.
 *
 *  Data which has not yet been written, but which is before the end-of-file
 *  (sparse data), shall read as zeroes.  A short read -- where the number of
 *  bytes read is less than requested -- indicates that the requested read was
 *  partially or, if zero bytes were read, entirely beyond the end-of-file.
 *
 *  If @p ullStart is at or beyond the maximum file size, it is treated like
 *  any other read entirely beyond the end-of-file: no data is read and
 *  @p pulLen is populated with zero.
 *
 *  @param ulInode  The inode number of the file to read.
 *  @param ullStart The file offset to read from.
 *  @param pulLen   On entry, contains the number of bytes to read; on
 *                  successful exit, contains the number of bytes actually
 *                  read.
 *  @param pBuffer  The buffer to populate with the data read.  Must be big
 *                  enough for the read request.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBADF  @p ulInode is not a valid inode number.
 *  @retval -RED_EINVAL The volume is not mounted; or @p pBuffer is `NULL`.
 *  @retval -RED_EIO    A disk I/O error occurred.
 *  @retval -RED_EISDIR The inode is a directory inode.
 */
REDSTATUS RedCoreFileRead( uint32_t ulInode,
                           uint64_t ullStart,
                           uint32_t * pulLen,
                           void * pBuffer )
{
    REDSTATUS ret;

    if( !gpRedVolume->fMounted || ( pulLen == NULL ) )
    {
        ret = -RED_EINVAL;
    }
    else
    {
        #if ( REDCONF_ATIME == 1 ) && ( REDCONF_READ_ONLY == 0 )
            bool fUpdateAtime = ( *pulLen > 0U ) && !gpRedVolume->fReadOnly;
        #else
            bool fUpdateAtime = false;
        #endif
        CINODE ino;

        ino.ulInode = ulInode;
        ret = RedInodeMount( &ino, FTYPE_FILE, fUpdateAtime );

        if( ret == 0 )
        {
            ret = RedInodeDataRead( &ino, ullStart, pulLen, pBuffer );

            #if ( REDCONF_ATIME == 1 ) && ( REDCONF_READ_ONLY == 0 )
                RedInodePut( &ino, ( ( ret == 0 ) && fUpdateAtime ) ? IPUT_UPDATE_ATIME : 0U );
            #else
                RedInodePut( &ino, 0U );
            #endif
        }
    }

    return ret;
}


#if REDCONF_READ_ONLY == 0

/** @brief Write to a file.
 *
 *  If the write extends beyond the end-of-file, the file size will be
 *  increased.
 *
 *  A short write -- where the number of bytes written is less than requested
 *  -- indicates either that the file system ran out of space but was still
 *  able to write some of the request; or that the request would have caused
 *  the file to exceed the maximum file size, but some of the data could be
 *  written prior to the file size limit.
 *
 *  If an error is returned, either none of the data was written or a critical
 *  error occurred (like an I/O error) and the file system volume will be
 *  read-only.
 *
 *  @param ulInode  The file number of the file to write.
 *  @param ullStart The file offset to write at.
 *  @param pulLen   On entry, the number of bytes to write; on successful exit,
 *                  the number of bytes actually written.
 *  @param pBuffer  The buffer containing the data to be written.  Must big
 *                  enough for the write request.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBADF  @p ulInode is not a valid file number.
 *  @retval -RED_EFBIG  No data can be written to the given file offset since
 *                      the resulting file size would exceed the maximum file
 *                      size.
 *  @retval -RED_EINVAL The volume is not mounted; or @p pBuffer is `NULL`.
 *  @retval -RED_EIO    A disk I/O error occurred.
 *  @retval -RED_EISDIR The inode is a directory inode.
 *  @retval -RED_ENOSPC No data can be written because there is insufficient
 *                      free space.
 *  @retval -RED_EROFS  The file system volume is read-only.
 */
    REDSTATUS RedCoreFileWrite( uint32_t ulInode,
                                uint64_t ullStart,
                                uint32_t * pulLen,
                                const void * pBuffer )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted )
        {
            ret = -RED_EINVAL;
        }
        else if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            ret = CoreFileWrite( ulInode, ullStart, pulLen, pBuffer );

            if( ( ret == -RED_ENOSPC ) &&
                ( ( gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL ) != 0U ) &&
                ( gpRedCoreVol->ulAlmostFreeBlocks > 0U ) )
            {
                ret = RedVolTransact();

                if( ret == 0 )
                {
                    ret = CoreFileWrite( ulInode, ullStart, pulLen, pBuffer );
                }
            }

            if( ( ret == 0 ) && ( ( gpRedVolume->ulTransMask & RED_TRANSACT_WRITE ) != 0U ) )
            {
                ret = RedVolTransact();
            }
        }

        return ret;
    }


/** @brief Write to a file.
 *
 *  @param ulInode  The file number of the file to write.
 *  @param ullStart The file offset to write at.
 *  @param pulLen   On entry, the number of bytes to write; on successful exit,
 *                  the number of bytes actually written.
 *  @param pBuffer  The buffer containing the data to be written.  Must big
 *                  enough for the write request.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBADF  @p ulInode is not a valid file number.
 *  @retval -RED_EFBIG  No data can be written to the given file offset since
 *                      the resulting file size would exceed the maximum file
 *                      size.
 *  @retval -RED_EIO    A disk I/O error occurred.
 *  @retval -RED_EISDIR The inode is a directory inode.
 *  @retval -RED_ENOSPC No data can be written because there is insufficient
 *                      free space.
 *  @retval -RED_EROFS  The file system volume is read-only.
 */
    static REDSTATUS CoreFileWrite( uint32_t ulInode,
                                    uint64_t ullStart,
                                    uint32_t * pulLen,
                                    const void * pBuffer )
    {
        REDSTATUS ret;

        if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            CINODE ino;

            ino.ulInode = ulInode;
            ret = RedInodeMount( &ino, FTYPE_FILE, true );

            if( ret == 0 )
            {
                ret = RedInodeDataWrite( &ino, ullStart, pulLen, pBuffer );

                RedInodePut( &ino, ( ret == 0 ) ? ( uint8_t ) ( IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME ) : 0U );
            }
        }

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


#if TRUNCATE_SUPPORTED

/** @brief Set the file size.
 *
 *  Allows the file size to be increased, decreased, or to remain the same.  If
 *  the file size is increased, the new area is sparse (will read as zeroes).
 *  If the file size is decreased, the data beyond the new end-of-file will
 *  return to free space once it is no longer part of the committed state
 *  (either immediately or after the next transaction point).
 *
 *  @param ulInode  The inode of the file to truncate.
 *  @param ullSize  The new file size, in bytes.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBADF  @p ulInode is not a valid inode number.
 *  @retval -RED_EFBIG  @p ullSize exceeds the maximum file size.
 *  @retval -RED_EINVAL The volume is not mounted.
 *  @retval -RED_EIO    A disk I/O error occurred.
 *  @retval -RED_EISDIR The inode is a directory inode.
 *  @retval -RED_ENOSPC Insufficient free space to perform the truncate.
 *  @retval -RED_EROFS  The file system volume is read-only.
 */
    REDSTATUS RedCoreFileTruncate( uint32_t ulInode,
                                   uint64_t ullSize )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted )
        {
            ret = -RED_EINVAL;
        }
        else if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            ret = CoreFileTruncate( ulInode, ullSize );

            if( ( ret == -RED_ENOSPC ) &&
                ( ( gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL ) != 0U ) &&
                ( gpRedCoreVol->ulAlmostFreeBlocks > 0U ) )
            {
                ret = RedVolTransact();

                if( ret == 0 )
                {
                    ret = CoreFileTruncate( ulInode, ullSize );
                }
            }

            if( ( ret == 0 ) && ( ( gpRedVolume->ulTransMask & RED_TRANSACT_TRUNCATE ) != 0U ) )
            {
                ret = RedVolTransact();
            }
        }

        return ret;
    }


/** @brief Set the file size.
 *
 *  @param ulInode  The inode of the file to truncate.
 *  @param ullSize  The new file size, in bytes.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBADF  @p ulInode is not a valid inode number.
 *  @retval -RED_EFBIG  @p ullSize exceeds the maximum file size.
 *  @retval -RED_EIO    A disk I/O error occurred.
 *  @retval -RED_EISDIR The inode is a directory inode.
 *  @retval -RED_ENOSPC Insufficient free space to perform the truncate.
 *  @retval -RED_EROFS  The file system volume is read-only.
 */
    static REDSTATUS CoreFileTruncate( uint32_t ulInode,
                                       uint64_t ullSize )
    {
        REDSTATUS ret;

        if( gpRedVolume->fReadOnly )
        {
            ret = -RED_EROFS;
        }
        else
        {
            CINODE ino;

            ino.ulInode = ulInode;
            ret = RedInodeMount( &ino, FTYPE_FILE, true );

            if( ret == 0 )
            {
                #if RESERVED_BLOCKS > 0U
                    gpRedCoreVol->fUseReservedBlocks = ( ullSize < ino.pInodeBuf->ullSize );
                #endif

                ret = RedInodeDataTruncate( &ino, ullSize );

                #if RESERVED_BLOCKS > 0U
                    gpRedCoreVol->fUseReservedBlocks = false;
                #endif

                RedInodePut( &ino, ( ret == 0 ) ? ( uint8_t ) ( IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME ) : 0U );
            }
        }

        return ret;
    }
#endif /* TRUNCATE_SUPPORTED */


#if ( REDCONF_API_POSIX == 1 ) && ( REDCONF_API_POSIX_READDIR == 1 )

/** @brief Read from a directory.
 *
 *  If files are added to the directory after it is opened, the new files may
 *  or may not be returned by this function.  If files are deleted, the deleted
 *  files will not be returned.
 *
 *  @param ulInode  The directory inode to read from.
 *  @param pulPos   A token which stores the position within the directory.  To
 *                  read from the beginning of the directory, populate with
 *                  zero.
 *  @param pszName  Pointer to a buffer which must be big enough to store a
 *                  maximum size name, including a null terminator.  On
 *                  successful exit, populated with the name of the next
 *                  directory entry.
 *  @param pulInode On successful return, populated with the inode number of the
 *                  next directory entry.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0               Operation was successful.
 *  @retval -RED_EBADF      @p ulInode is not a valid inode number.
 *  @retval -RED_EINVAL     The volume is not mounted.
 *  @retval -RED_EIO        A disk I/O error occurred.
 *  @retval -RED_ENOENT     There are no more entries in the directory.
 *  @retval -RED_ENOTDIR    @p ulInode refers to a file.
 */
    REDSTATUS RedCoreDirRead( uint32_t ulInode,
                              uint32_t * pulPos,
                              char * pszName,
                              uint32_t * pulInode )
    {
        REDSTATUS ret;

        if( !gpRedVolume->fMounted )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            CINODE ino;

            ino.ulInode = ulInode;
            ret = RedInodeMount( &ino, FTYPE_DIR, false );

            if( ret == 0 )
            {
                ret = RedDirEntryRead( &ino, pulPos, pszName, pulInode );

                #if ( REDCONF_ATIME == 1 ) && ( REDCONF_READ_ONLY == 0 )
                    if( ( ret == 0 ) && !gpRedVolume->fReadOnly )
                    {
                        ret = RedInodeBranch( &ino );
                    }

                    RedInodePut( &ino, ( ( ret == 0 ) && !gpRedVolume->fReadOnly ) ? IPUT_UPDATE_ATIME : 0U );
                #else
                    RedInodePut( &ino, 0U );
                #endif
            }
        }

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