/*             ----> 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
        }
      #endif

        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_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_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 refered 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 refered 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(ret == 0)
              #endif
                {
                    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) */

