/*             ----> 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 Implementation of the the Reliance Edge POSIX-like API.
 */

#include <redfs.h>

#if REDCONF_API_POSIX == 1

/** @defgroup red_group_posix The POSIX-like File System Interface
 *  @{
 */

    #include <redvolume.h>
    #include <redcoreapi.h>
    #include <redposix.h>
    #include <redpath.h>


/*-------------------------------------------------------------------
 *   File Descriptors
 *  -------------------------------------------------------------------*/

    #define FD_GEN_BITS    11U /* File descriptor bits for mount generation. */
    #define FD_VOL_BITS    8U  /* File descriptor bits for volume number. */
    #define FD_IDX_BITS    12U /* File descriptor bits for handle index. */

/*  31 bits available: file descriptors are int32_t, but the sign bit must
 *  always be zero.
 */
    #if ( FD_GEN_BITS + FD_VOL_BITS + FD_IDX_BITS ) > 31U
        #error "Internal error: too many file descriptor bits!"
    #endif

/*  Maximum values for file descriptor components.
 */
    #define FD_GEN_MAX    ( ( 1UL << FD_GEN_BITS ) - 1U )
    #define FD_VOL_MAX    ( ( 1UL << FD_VOL_BITS ) - 1U )
    #define FD_IDX_MAX    ( ( 1UL << FD_IDX_BITS ) - 1U )

    #if REDCONF_VOLUME_COUNT > FD_VOL_MAX
        #error "Error: Too many file system volumes!"
    #endif
    #if REDCONF_HANDLE_COUNT > ( FD_IDX_MAX + 1U )
        #error "Error: Too many file system handles!"
    #endif

/*  File descriptors must never be negative; and must never be zero, one, or
 *  two, to avoid confusion with STDIN, STDOUT, and STDERR.
 */
    #define FD_MIN    ( 3 )

/*-------------------------------------------------------------------
 *   Handles
 *  -------------------------------------------------------------------*/

/*  Mask of all RED_O_* values.
 */
    #define RED_O_MASK         ( RED_O_RDONLY | RED_O_WRONLY | RED_O_RDWR | RED_O_APPEND | RED_O_CREAT | RED_O_EXCL | RED_O_TRUNC )

    #define HFLAG_DIRECTORY    0x01U /* Handle is for a directory. */
    #define HFLAG_READABLE     0x02U /* Handle is readable. */
    #define HFLAG_WRITEABLE    0x04U /* Handle is writeable. */
    #define HFLAG_APPENDING    0x08U /* Handle was opened in append mode. */

/*  @brief Handle structure, used to implement file descriptors and directory
 *         streams.
 */
    typedef struct sREDHANDLE
    {
        uint32_t ulInode;     /**< Inode number; 0 if handle is available. */
        uint8_t bVolNum;      /**< Volume containing the inode. */
        uint8_t bFlags;       /**< Handle flags (type and mode). */
        uint64_t ullOffset;   /**< File or directory offset. */
        #if REDCONF_API_POSIX_READDIR == 1
            REDDIRENT dirent; /**< Dirent structure returned by red_readdir(). */
        #endif
    } REDHANDLE;

/*-------------------------------------------------------------------
 *   Tasks
 *  -------------------------------------------------------------------*/

    #if REDCONF_TASK_COUNT > 1U

/*  @brief Per-task information.
 */
        typedef struct
        {
            uint32_t ulTaskId; /**< ID of the task which owns this slot; 0 if free. */
            REDSTATUS iErrno;  /**< Last error value. */
        } TASKSLOT;
    #endif

/*-------------------------------------------------------------------
 *   Local Prototypes
 *  -------------------------------------------------------------------*/

    #if ( REDCONF_READ_ONLY == 0 ) && ( ( REDCONF_API_POSIX_UNLINK == 1 ) || ( REDCONF_API_POSIX_RMDIR == 1 ) )
        static REDSTATUS UnlinkSub( const char * pszPath,
                                    FTYPE type );
    #endif
    static REDSTATUS FildesOpen( const char * pszPath,
                                 uint32_t ulOpenMode,
                                 FTYPE type,
                                 int32_t * piFildes );
    static REDSTATUS FildesClose( int32_t iFildes );
    static REDSTATUS FildesToHandle( int32_t iFildes,
                                     FTYPE expectedType,
                                     REDHANDLE ** ppHandle );
    static int32_t FildesPack( uint16_t uHandleIdx,
                               uint8_t bVolNum );
    static void FildesUnpack( int32_t iFildes,
                              uint16_t * puHandleIdx,
                              uint8_t * pbVolNum,
                              uint16_t * puGeneration );
    #if REDCONF_API_POSIX_READDIR == 1
        static bool DirStreamIsValid( const REDDIR * pDirStream );
    #endif
    static REDSTATUS PosixEnter( void );
    static void PosixLeave( void );
    static REDSTATUS ModeTypeCheck( uint16_t uMode,
                                    FTYPE expectedType );
    #if ( REDCONF_READ_ONLY == 0 ) && ( ( REDCONF_API_POSIX_UNLINK == 1 ) || ( REDCONF_API_POSIX_RMDIR == 1 ) || ( ( REDCONF_API_POSIX_RENAME == 1 ) && ( REDCONF_RENAME_ATOMIC == 1 ) ) )
        static REDSTATUS InodeUnlinkCheck( uint32_t ulInode );
    #endif
    #if REDCONF_TASK_COUNT > 1U
        static REDSTATUS TaskRegister( uint32_t * pulTaskIdx );
    #endif
    static int32_t PosixReturn( REDSTATUS iError );

/*-------------------------------------------------------------------
 *   Globals
 *  -------------------------------------------------------------------*/

    static bool gfPosixInited;                         /* Whether driver is initialized. */
    static REDHANDLE gaHandle[ REDCONF_HANDLE_COUNT ]; /* Array of all handles. */
    #if REDCONF_TASK_COUNT > 1U
        static TASKSLOT gaTask[ REDCONF_TASK_COUNT ];  /* Array of task slots. */
    #endif

/*  Array of volume mount "generations".  These are incremented for a volume
 *  each time that volume is mounted.  The generation number (along with the
 *  volume number) is incorporated into the file descriptors; a stale file
 *  descriptor from a previous mount can be detected since it will include a
 *  stale generation number.
 */
    static uint16_t gauGeneration[ REDCONF_VOLUME_COUNT ];


/*-------------------------------------------------------------------
 *   Public API
 *  -------------------------------------------------------------------*/

/** @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 or formatted
 *  until the driver has been initialized.
 *
 *  If this function is called when the Reliance Edge driver is already
 *  initialized, it does nothing and returns success.
 *
 *  This function is not thread safe: attempting to initialize from multiple
 *  threads could leave things in a bad state.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EINVAL: The volume path prefix configuration is invalid.
 */
    int32_t red_init( void )
    {
        REDSTATUS ret;

        if( gfPosixInited )
        {
            ret = 0;
        }
        else
        {
            ret = RedCoreInit();

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

                #if REDCONF_TASK_COUNT > 1U
                    RedMemSet( gaTask, 0U, sizeof( gaTask ) );
                #endif

                gfPosixInited = true;
            }
        }

        return PosixReturn( 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 red_init() will
 *  initialize the driver again.
 *
 *  If this function is called when the Reliance Edge driver is already
 *  uninitialized, it does nothing and returns success.
 *
 *  This function is not thread safe: attempting to uninitialize from multiple
 *  threads could leave things in a bad state.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBUSY: At least one volume is still mounted.
 */
    int32_t red_uninit( void )
    {
        REDSTATUS ret;

        if( gfPosixInited )
        {
            ret = PosixEnter();

            if( ret == 0 )
            {
                uint8_t bVolNum;

                for( bVolNum = 0U; bVolNum < REDCONF_VOLUME_COUNT; bVolNum++ )
                {
                    if( gaRedVolume[ bVolNum ].fMounted )
                    {
                        ret = -RED_EBUSY;
                        break;
                    }
                }

                if( ret == 0 )
                {
                    /*  All volumes are unmounted.  Mark the driver as
                     *  uninitialized before releasing the FS mutex, to avoid any
                     *  race condition where a volume could be mounted and then the
                     *  driver uninitialized with a mounted volume.
                     */
                    gfPosixInited = false;
                }

                /*  The FS mutex must be released before we uninitialize the core,
                 *  since the FS mutex needs to be in the released state when it
                 *  gets uninitialized.
                 *
                 *  Don't use PosixLeave(), since it asserts gfPosixInited is true.
                 */
                #if REDCONF_TASK_COUNT > 1U
                    RedOsMutexRelease();
                #endif
            }

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

                /*  Not good if the above fails, since things might be partly, but
                 *  not entirely, torn down, and there might not be a way back to
                 *  a valid driver state.
                 */
                REDASSERT( ret == 0 );
            }
        }
        else
        {
            ret = 0;
        }

        return PosixReturn( ret );
    }


/** @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.
 *
 *  An error is returned if the volume is already mounted.
 *
 *  @param pszVolume    A path prefix identifying the volume to mount.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBUSY: Volume is already mounted.
 *  - #RED_EINVAL: @p pszVolume is `NULL`; or the driver is uninitialized.
 *  - #RED_EIO: Volume not formatted, improperly formatted, or corrupt.
 *  - #RED_ENOENT: @p pszVolume is not a valid volume path prefix.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
    int32_t red_mount( const char * pszVolume )
    {
        REDSTATUS ret;

        ret = PosixEnter();

        if( ret == 0 )
        {
            uint8_t bVolNum;

            ret = RedPathSplit( pszVolume, &bVolNum, NULL );

            /*  The core will return success if the volume is already mounted, so
             *  check for that condition here to propagate the error.
             */
            if( ( ret == 0 ) && gaRedVolume[ bVolNum ].fMounted )
            {
                ret = -RED_EBUSY;
            }

            #if REDCONF_VOLUME_COUNT > 1U
                if( ret == 0 )
                {
                    ret = RedCoreVolSetCurrent( bVolNum );
                }
            #endif

            if( ret == 0 )
            {
                ret = RedCoreVolMount();
            }

            if( ret == 0 )
            {
                /*  Increment the mount generation, invalidating file descriptors
                 *  from previous mounts.  Note that while the generation numbers
                 *  are stored in 16-bit values, we have less than 16-bits to store
                 *  generations in the file descriptors, so we must wrap-around
                 *  manually.
                 */
                gauGeneration[ bVolNum ]++;

                if( gauGeneration[ bVolNum ] > FD_GEN_MAX )
                {
                    /*  Wrap-around to one, rather than zero.  The generation is
                     *  stored in the top bits of the file descriptor, and doing
                     *  this means that low numbers are never valid file
                     *  descriptors.  This implements the requirement that 0, 1,
                     *  and 2 are never valid file descriptors, thereby avoiding
                     *  confusion with STDIN, STDOUT, and STDERR.
                     */
                    gauGeneration[ bVolNum ] = 1U;
                }
            }

            PosixLeave();
        }

        return PosixReturn( ret );
    }


/** @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.
 *
 *  Before unmounting, this function will wait for any active file system
 *  thread to complete by acquiring the FS mutex.  The volume will be marked as
 *  unmounted before the FS mutex is released, so subsequent FS threads will
 *  possibly block and then see an error when attempting to access a volume
 *  which is unmounting or unmounted.  If the volume has open handles, the
 *  unmount will fail.
 *
 *  An error is returned if the volume is already unmounted.
 *
 *  @param pszVolume    A path prefix identifying the volume to unmount.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBUSY: There are still open handles for this file system volume.
 *  - #RED_EINVAL: @p pszVolume is `NULL`; or the driver is uninitialized; or
 *    the volume is already unmounted.
 *  - #RED_EIO: I/O error during unmount automatic transaction point.
 *  - #RED_ENOENT: @p pszVolume is not a valid volume path prefix.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
    int32_t red_umount( const char * pszVolume )
    {
        REDSTATUS ret;

        ret = PosixEnter();

        if( ret == 0 )
        {
            uint8_t bVolNum;

            ret = RedPathSplit( pszVolume, &bVolNum, NULL );

            /*  The core will return success if the volume is already unmounted, so
             *  check for that condition here to propagate the error.
             */
            if( ( ret == 0 ) && !gaRedVolume[ bVolNum ].fMounted )
            {
                ret = -RED_EINVAL;
            }

            if( ret == 0 )
            {
                uint16_t uHandleIdx;

                /*  Do not unmount the volume if it still has open handles.
                 */
                for( uHandleIdx = 0U; uHandleIdx < REDCONF_HANDLE_COUNT; uHandleIdx++ )
                {
                    const REDHANDLE * pHandle = &gaHandle[ uHandleIdx ];

                    if( ( pHandle->ulInode != INODE_INVALID ) && ( pHandle->bVolNum == bVolNum ) )
                    {
                        ret = -RED_EBUSY;
                        break;
                    }
                }
            }

            #if REDCONF_VOLUME_COUNT > 1U
                if( ret == 0 )
                {
                    ret = RedCoreVolSetCurrent( bVolNum );
                }
            #endif

            if( ret == 0 )
            {
                ret = RedCoreVolUnmount();
            }

            PosixLeave();
        }

        return PosixReturn( ret );
    }


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

/** @brief Format a file system volume.
 *
 *  Uses the statically defined volume configuration.  After calling this
 *  function, the volume needs to be mounted -- see red_mount().
 *
 *  An error is returned if the volume is mounted.
 *
 *  @param pszVolume    A path prefix identifying the volume to format.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBUSY: Volume is mounted.
 *  - #RED_EINVAL: @p pszVolume is `NULL`; or the driver is uninitialized.
 *  - #RED_EIO: I/O error formatting the volume.
 *  - #RED_ENOENT: @p pszVolume is not a valid volume path prefix.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_format( const char * pszVolume )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                uint8_t bVolNum;

                ret = RedPathSplit( pszVolume, &bVolNum, NULL );

                #if REDCONF_VOLUME_COUNT > 1U
                    if( ret == 0 )
                    {
                        ret = RedCoreVolSetCurrent( bVolNum );
                    }
                #endif

                if( ret == 0 )
                {
                    ret = RedCoreVolFormat();
                }

                PosixLeave();
            }

            return PosixReturn( ret );
        }
    #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_FORMAT == 1 ) */


    #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.
 *
 *  @param pszVolume    A path prefix identifying the volume to transact.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EINVAL: Volume is not mounted; or @p pszVolume is `NULL`.
 *  - #RED_EIO: I/O error during the transaction point.
 *  - #RED_ENOENT: @p pszVolume is not a valid volume path prefix.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_transact( const char * pszVolume )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                uint8_t bVolNum;

                ret = RedPathSplit( pszVolume, &bVolNum, NULL );

                #if REDCONF_VOLUME_COUNT > 1U
                    if( ret == 0 )
                    {
                        ret = RedCoreVolSetCurrent( bVolNum );
                    }
                #endif

                if( ret == 0 )
                {
                    ret = RedCoreVolTransact();
                }

                PosixLeave();
            }

            return PosixReturn( ret );
        }
    #endif /* if REDCONF_READ_ONLY == 0 */


    #if REDCONF_READ_ONLY == 0

/** @brief Update the transaction mask.
 *
 *  The following events are available:
 *
 *  - #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 pszVolume    The path prefix of the volume whose transaction mask is
 *                      being changed.
 *  @param ulEventMask  A bitwise-OR'd mask of automatic transaction events to
 *                      be set as the current transaction mode.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EINVAL: Volume is not mounted; or @p pszVolume is `NULL`; or
 *    @p ulEventMask contains invalid bits.
 *  - #RED_ENOENT: @p pszVolume is not a valid volume path prefix.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_settransmask( const char * pszVolume,
                                  uint32_t ulEventMask )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                uint8_t bVolNum;

                ret = RedPathSplit( pszVolume, &bVolNum, NULL );

                #if REDCONF_VOLUME_COUNT > 1U
                    if( ret == 0 )
                    {
                        ret = RedCoreVolSetCurrent( bVolNum );
                    }
                #endif

                if( ret == 0 )
                {
                    ret = RedCoreTransMaskSet( ulEventMask );
                }

                PosixLeave();
            }

            return PosixReturn( ret );
        }
    #endif /* if REDCONF_READ_ONLY == 0 */


/** @brief Read the transaction mask.
 *
 *  If the volume is read-only, the returned event mask is always zero.
 *
 *  @param pszVolume    The path prefix of the volume whose transaction mask is
 *                      being retrieved.
 *  @param pulEventMask Populated with a bitwise-OR'd mask of automatic
 *                      transaction events which represent the current
 *                      transaction mode for the volume.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EINVAL: Volume is not mounted; or @p pszVolume is `NULL`; or
 *    @p pulEventMask is `NULL`.
 *  - #RED_ENOENT: @p pszVolume is not a valid volume path prefix.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
    int32_t red_gettransmask( const char * pszVolume,
                              uint32_t * pulEventMask )
    {
        REDSTATUS ret;

        ret = PosixEnter();

        if( ret == 0 )
        {
            uint8_t bVolNum;

            ret = RedPathSplit( pszVolume, &bVolNum, NULL );

            #if REDCONF_VOLUME_COUNT > 1U
                if( ret == 0 )
                {
                    ret = RedCoreVolSetCurrent( bVolNum );
                }
            #endif

            if( ret == 0 )
            {
                ret = RedCoreTransMaskGet( pulEventMask );
            }

            PosixLeave();
        }

        return PosixReturn( ret );
    }


/** @brief Query file system status information.
 *
 *  @p pszVolume should name a valid volume prefix or a valid root directory;
 *  this differs from POSIX statvfs, where any existing file or directory is a
 *  valid path.
 *
 *  @param pszVolume    The path prefix of the volume to query.
 *  @param pStatvfs     The buffer to populate with volume information.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EINVAL: Volume is not mounted; or @p pszVolume is `NULL`; or
 *    @p pStatvfs is `NULL`.
 *  - #RED_ENOENT: @p pszVolume is not a valid volume path prefix.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
    int32_t red_statvfs( const char * pszVolume,
                         REDSTATFS * pStatvfs )
    {
        REDSTATUS ret;

        ret = PosixEnter();

        if( ret == 0 )
        {
            uint8_t bVolNum;

            ret = RedPathSplit( pszVolume, &bVolNum, NULL );

            #if REDCONF_VOLUME_COUNT > 1U
                if( ret == 0 )
                {
                    ret = RedCoreVolSetCurrent( bVolNum );
                }
            #endif

            if( ret == 0 )
            {
                ret = RedCoreVolStat( pStatvfs );
            }

            PosixLeave();
        }

        return PosixReturn( ret );
    }


/** @brief Open a file or directory.
 *
 *  Exactly one file access mode must be specified:
 *
 *  - #RED_O_RDONLY: Open for reading only.
 *  - #RED_O_WRONLY: Open for writing only.
 *  - #RED_O_RDWR: Open for reading and writing.
 *
 *  Directories can only be opened with `RED_O_RDONLY`.
 *
 *  The following flags may also be used:
 *
 *  - #RED_O_APPEND: Set the file offset to the end-of-file prior to each
 *    write.
 *  - #RED_O_CREAT: Create the named file if it does not exist.
 *  - #RED_O_EXCL: In combination with `RED_O_CREAT`, return an error if the
 *    path already exists.
 *  - #RED_O_TRUNC: Truncate the opened file to size zero.  Only supported when
 #REDCONF_API_POSIX_FTRUNCATE is true.
 *
 #RED_O_CREAT, #RED_O_EXCL, and #RED_O_TRUNC are invalid with #RED_O_RDONLY.
 #RED_O_EXCL is invalid without #RED_O_CREAT.
 *
 *  If the volume is read-only, #RED_O_RDONLY is the only valid open flag; use
 *  of any other flag will result in an error.
 *
 *  If #RED_O_TRUNC frees data which is in the committed state, it will not
 *  return to free space until after a transaction point.
 *
 *  The returned file descriptor must later be closed with red_close().
 *
 *  Unlike POSIX open, there is no optional third argument for the permissions
 *  (which Reliance Edge does not use) and other open flags (like `O_SYNC`) are
 *  not supported.
 *
 *  @param pszPath      The path to the file or directory.
 *  @param ulOpenMode   The open flags (mask of `RED_O_` values).
 *
 *  @return On success, a nonnegative file descriptor is returned.  On error,
 *          -1 is returned and #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EEXIST: Using #RED_O_CREAT and #RED_O_EXCL, and the indicated path
 *    already exists.
 *  - #RED_EINVAL: @p ulOpenMode is invalid; or @p pszPath is `NULL`; or the
 *    volume containing the path is not mounted.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_EISDIR: The path names a directory and @p ulOpenMode includes
 #RED_O_WRONLY or #RED_O_RDWR.
 *  - #RED_EMFILE: There are no available file descriptors.
 *  - #RED_ENAMETOOLONG: The length of a component of @p pszPath is longer than
 #REDCONF_NAME_MAX.
 *  - #RED_ENFILE: Attempting to create a file but the file system has used all
 *    available inode slots.
 *  - #RED_ENOENT: #RED_O_CREAT is not set and the named file does not exist; or
 #RED_O_CREAT is set and the parent directory does not exist; or the
 *    volume does not exist; or the @p pszPath argument, after removing the
 *    volume prefix, points to an empty string.
 *  - #RED_ENOSPC: The file does not exist and #RED_O_CREAT was specified, but
 *    there is insufficient free space to expand the directory or to create the
 *    new file.
 *  - #RED_ENOTDIR: A component of the prefix in @p pszPath does not name a
 *    directory.
 *  - #RED_EROFS: The path resides on a read-only file system and a write
 *    operation was requested.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
    int32_t red_open( const char * pszPath,
                      uint32_t ulOpenMode )
    {
        int32_t iFildes = -1; /* Init'd to quiet warnings. */
        REDSTATUS ret;

        #if REDCONF_READ_ONLY == 1
            if( ulOpenMode != RED_O_RDONLY )
            {
                ret = -RED_EROFS;
            }
        #else
            if( ( ulOpenMode != ( ulOpenMode & RED_O_MASK ) ) ||
                ( ( ulOpenMode & ( RED_O_RDONLY | RED_O_WRONLY | RED_O_RDWR ) ) == 0U ) ||
                ( ( ( ulOpenMode & RED_O_RDONLY ) != 0U ) && ( ( ulOpenMode & ( RED_O_WRONLY | RED_O_RDWR ) ) != 0U ) ) ||
                ( ( ( ulOpenMode & RED_O_WRONLY ) != 0U ) && ( ( ulOpenMode & ( RED_O_RDONLY | RED_O_RDWR ) ) != 0U ) ) ||
                ( ( ( ulOpenMode & RED_O_RDWR ) != 0U ) && ( ( ulOpenMode & ( RED_O_RDONLY | RED_O_WRONLY ) ) != 0U ) ) ||
                ( ( ( ulOpenMode & ( RED_O_TRUNC | RED_O_CREAT | RED_O_EXCL ) ) != 0U ) && ( ( ulOpenMode & RED_O_RDONLY ) != 0U ) ) ||
                ( ( ( ulOpenMode & RED_O_EXCL ) != 0U ) && ( ( ulOpenMode & RED_O_CREAT ) == 0U ) ) )
            {
                ret = -RED_EINVAL;
            }

            #if REDCONF_API_POSIX_FTRUNCATE == 0
                else if( ( ulOpenMode & RED_O_TRUNC ) != 0U )
                {
                    ret = -RED_EINVAL;
                }
            #endif
        #endif /* if REDCONF_READ_ONLY == 1 */
        else
        {
            ret = PosixEnter();
        }

        if( ret == 0 )
        {
            ret = FildesOpen( pszPath, ulOpenMode, FTYPE_EITHER, &iFildes );

            PosixLeave();
        }

        if( ret != 0 )
        {
            iFildes = PosixReturn( ret );
        }

        return iFildes;
    }


    #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_UNLINK == 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.
 *
 *  Unlike POSIX unlink, deleting a file or directory with open handles (file
 *  descriptors or directory streams) will fail with an #RED_EBUSY error.  This
 *  only applies when deleting an inode with a link count of one; if a file has
 *  multiple names (hard links), all but the last name may be deleted even if
 *  the file is open.
 *
 *  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.
 *
 *  Unlike POSIX unlink, 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 pszPath  The path of the file or directory to delete.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBUSY: @p pszPath points to an inode with open handles and a link
 *    count of one.
 *  - #RED_EINVAL: @p pszPath is `NULL`; or the volume containing the path is
 *    not mounted.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_ENAMETOOLONG: The length of a component of @p pszPath is longer than
 #REDCONF_NAME_MAX.
 *  - #RED_ENOENT: The path does not name an existing file; or the @p pszPath
 *    argument, after removing the volume prefix, points to an empty string.
 *  - #RED_ENOTDIR: A component of the path prefix is not a directory.
 *  - #RED_ENOTEMPTY: The path names a directory which is not empty.
 *  - #RED_ENOSPC: The file system does not have enough space to modify the
 *    parent directory to perform the deletion.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_unlink( const char * pszPath )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                ret = UnlinkSub( pszPath, FTYPE_EITHER );

                PosixLeave();
            }

            return PosixReturn( ret );
        }
    #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_UNLINK == 1 ) */


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

/** @brief Create a new directory.
 *
 *  Unlike POSIX mkdir, this function has no second argument for the
 *  permissions (which Reliance Edge does not use).
 *
 *  @param pszPath  The name and location of the directory to create.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EEXIST: @p pszPath points to an existing file or directory.
 *  - #RED_EINVAL: @p pszPath is `NULL`; or the volume containing the path is
 *    not mounted.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_ENAMETOOLONG: The length of a component of @p pszPath is longer than
 #REDCONF_NAME_MAX.
 *  - #RED_ENOENT: A component of the path prefix does not name an existing
 *    directory; or the @p pszPath argument, after removing the volume prefix,
 *    points to an empty string.
 *  - #RED_ENOSPC: The file system does not have enough space for the new
 *    directory or to extend the parent directory of the new directory.
 *  - #RED_ENOTDIR: A component of the path prefix is not a directory.
 *  - #RED_EROFS: The parent directory resides on a read-only file system.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_mkdir( const char * pszPath )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                const char * pszLocalPath;
                uint8_t bVolNum;

                ret = RedPathSplit( pszPath, &bVolNum, &pszLocalPath );

                #if REDCONF_VOLUME_COUNT > 1U
                    if( ret == 0 )
                    {
                        ret = RedCoreVolSetCurrent( bVolNum );
                    }
                #endif

                if( ret == 0 )
                {
                    const char * pszName;
                    uint32_t ulPInode;

                    ret = RedPathToName( pszLocalPath, &ulPInode, &pszName );

                    if( ret == 0 )
                    {
                        uint32_t ulInode;

                        ret = RedCoreCreate( ulPInode, pszName, true, &ulInode );
                    }
                }

                PosixLeave();
            }

            return PosixReturn( ret );
        }
    #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_MKDIR == 1 ) */


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

/** @brief Delete a directory.
 *
 *  The given directory name is deleted and the corresponding directory inode
 *  will be deleted.
 *
 *  Unlike POSIX rmdir, deleting a directory with open handles (file
 *  descriptors or directory streams) will fail with an #RED_EBUSY error.
 *
 *  If the path names a directory which is not empty, the deletion will fail.
 *  If the path names the root directory of a file system volume, the deletion
 *  will fail.
 *
 *  If the path names a regular file, the deletion will fail.  This provides
 *  type checking and may be useful in cases where an application knows the
 *  path to be deleted should name a directory.
 *
 *  If the deletion frees data in the committed state, it will not return to
 *  free space until after a transaction point.
 *
 *  Unlike POSIX rmdir, 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 pszPath  The path of the directory to delete.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBUSY: @p pszPath points to a directory with open handles.
 *  - #RED_EINVAL: @p pszPath is `NULL`; or the volume containing the path is
 *    not mounted.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_ENAMETOOLONG: The length of a component of @p pszPath is longer than
 #REDCONF_NAME_MAX.
 *  - #RED_ENOENT: The path does not name an existing directory; or the
 *    @p pszPath argument, after removing the volume prefix, points to an empty
 *    string.
 *  - #RED_ENOTDIR: A component of the path is not a directory.
 *  - #RED_ENOTEMPTY: The path names a directory which is not empty.
 *  - #RED_ENOSPC: The file system does not have enough space to modify the
 *    parent directory to perform the deletion.
 *  - #RED_EROFS: The directory to be removed resides on a read-only file
 *    system.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_rmdir( const char * pszPath )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                ret = UnlinkSub( pszPath, FTYPE_DIR );

                PosixLeave();
            }

            return PosixReturn( ret );
        }
    #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_RMDIR == 1 ) */


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

/** @brief Rename a file or directory.
 *
 *  Both paths must reside on the same file system volume.  Attempting to use
 *  this API to move a file to a different volume will result in an error.
 *
 *  If @p pszNewPath 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 and sets #red_errno to
 #RED_EEXIST.  This behavior is contrary to POSIX.
 *
 *  If #REDCONF_RENAME_ATOMIC is true, and if the new name exists, then in one
 *  atomic operation, @p pszNewPath is unlinked and @p pszOldPath is renamed to
 *  @p pszNewPath.  Both @p pszNewPath and @p pszOldPath must be of the same
 *  type (both files or both directories).  As with red_unlink(), if
 *  @p pszNewPath is a directory, it must be empty.  The major exception to this
 *  behavior is that if both @p pszOldPath and @p pszNewPath are links to the
 *  same inode, then the rename does nothing and both names continue to exist.
 *  Unlike POSIX rename, if @p pszNewPath points to an inode with a link count
 *  of one and open handles (file descriptors or directory streams), the
 *  rename will fail with #RED_EBUSY.
 *
 *  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 pszOldPath   The path of the file or directory to rename.
 *  @param pszNewPath   The new name and location after the rename.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBUSY: #REDCONF_RENAME_ATOMIC is true and @p pszNewPath points to an
 *    inode with open handles and a link count of one.
 *  - #RED_EEXIST: #REDCONF_RENAME_ATOMIC is false and @p pszNewPath exists.
 *  - #RED_EINVAL: @p pszOldPath is `NULL`; or @p pszNewPath is `NULL`; or the
 *    volume containing the path is not mounted.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_EISDIR: The @p pszNewPath argument names a directory and the
 *    @p pszOldPath argument names a non-directory.
 *  - #RED_ENAMETOOLONG: The length of a component of either @p pszOldPath or
 *    @p pszNewPath is longer than #REDCONF_NAME_MAX.
 *  - #RED_ENOENT: The link named by @p pszOldPath does not name an existing
 *    entry; or either @p pszOldPath or @p pszNewPath, after removing the volume
 *    prefix, point to an empty string.
 *  - #RED_ENOTDIR: A component of either path prefix is not a directory; or
 *    @p pszOldPath names a directory and @p pszNewPath names a file.
 *  - #RED_ENOTEMPTY: The path named by @p pszNewPath is a directory which is
 *    not empty.
 *  - #RED_ENOSPC: The file system does not have enough space to extend the
 *    directory that would contain @p pszNewPath.
 *  - #RED_EROFS: The directory to be removed resides on a read-only file
 *    system.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 *  - #RED_EXDEV: @p pszOldPath and @p pszNewPath are on different file system
 *    volumes.
 */
        int32_t red_rename( const char * pszOldPath,
                            const char * pszNewPath )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                const char * pszOldLocalPath;
                uint8_t bOldVolNum;

                ret = RedPathSplit( pszOldPath, &bOldVolNum, &pszOldLocalPath );

                if( ret == 0 )
                {
                    const char * pszNewLocalPath;
                    uint8_t bNewVolNum;

                    ret = RedPathSplit( pszNewPath, &bNewVolNum, &pszNewLocalPath );

                    if( ( ret == 0 ) && ( bOldVolNum != bNewVolNum ) )
                    {
                        ret = -RED_EXDEV;
                    }

                    #if REDCONF_VOLUME_COUNT > 1U
                        if( ret == 0 )
                        {
                            ret = RedCoreVolSetCurrent( bOldVolNum );
                        }
                    #endif

                    if( ret == 0 )
                    {
                        const char * pszOldName;
                        uint32_t ulOldPInode;

                        ret = RedPathToName( pszOldLocalPath, &ulOldPInode, &pszOldName );

                        if( ret == 0 )
                        {
                            const char * pszNewName;
                            uint32_t ulNewPInode;

                            ret = RedPathToName( pszNewLocalPath, &ulNewPInode, &pszNewName );

                            #if REDCONF_RENAME_ATOMIC == 1
                                if( ret == 0 )
                                {
                                    uint32_t ulDestInode;

                                    ret = RedCoreLookup( ulNewPInode, pszNewName, &ulDestInode );

                                    if( ret == 0 )
                                    {
                                        ret = InodeUnlinkCheck( ulDestInode );
                                    }
                                    else if( ret == -RED_ENOENT )
                                    {
                                        ret = 0;
                                    }
                                    else
                                    {
                                        /*  Unexpected error, nothing to do.
                                         */
                                    }
                                }
                            #endif /* if REDCONF_RENAME_ATOMIC == 1 */

                            if( ret == 0 )
                            {
                                ret = RedCoreRename( ulOldPInode, pszOldName, ulNewPInode, pszNewName );
                            }
                        }
                    }
                }

                PosixLeave();
            }

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


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

/** @brief Create a hard link.
 *
 *  This creates an additional name (link) for the file named by @p pszPath.
 *  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 red_fstat()) 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 pszPath names a directory, the operation will fail.
 *
 *  @param pszPath      The path indicating the inode for the new link.
 *  @param pszHardLink  The name and location for the new link.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EEXIST: @p pszHardLink resolves to an existing file.
 *  - #RED_EINVAL: @p pszPath or @p pszHardLink is `NULL`; or the volume
 *    containing the paths is not mounted.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_EMLINK: Creating the link would exceed the maximum link count of the
 *    inode named by @p pszPath.
 *  - #RED_ENAMETOOLONG: The length of a component of either @p pszPath or
 *    @p pszHardLink is longer than #REDCONF_NAME_MAX.
 *  - #RED_ENOENT: A component of either path prefix does not exist; or the file
 *    named by @p pszPath does not exist; or either @p pszPath or
 *    @p pszHardLink, after removing the volume prefix, point to an empty
 *    string.
 *  - #RED_ENOSPC: There is insufficient free space to expand the directory that
 *    would contain the link.
 *  - #RED_ENOTDIR: A component of either path prefix is not a directory.
 *  - #RED_EPERM: The @p pszPath argument names a directory.
 *  - #RED_EROFS: The requested link requires writing in a directory on a
 *    read-only file system.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 *  - #RED_EXDEV: @p pszPath and @p pszHardLink are on different file system
 *    volumes.
 */
        int32_t red_link( const char * pszPath,
                          const char * pszHardLink )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                const char * pszLocalPath;
                uint8_t bVolNum;

                ret = RedPathSplit( pszPath, &bVolNum, &pszLocalPath );

                if( ret == 0 )
                {
                    const char * pszLinkLocalPath;
                    uint8_t bLinkVolNum;

                    ret = RedPathSplit( pszHardLink, &bLinkVolNum, &pszLinkLocalPath );

                    if( ( ret == 0 ) && ( bVolNum != bLinkVolNum ) )
                    {
                        ret = -RED_EXDEV;
                    }

                    #if REDCONF_VOLUME_COUNT > 1U
                        if( ret == 0 )
                        {
                            ret = RedCoreVolSetCurrent( bVolNum );
                        }
                    #endif

                    if( ret == 0 )
                    {
                        uint32_t ulInode;

                        ret = RedPathLookup( pszLocalPath, &ulInode );

                        if( ret == 0 )
                        {
                            const char * pszLinkName;
                            uint32_t ulLinkPInode;

                            ret = RedPathToName( pszLinkLocalPath, &ulLinkPInode, &pszLinkName );

                            if( ret == 0 )
                            {
                                ret = RedCoreLink( ulLinkPInode, pszLinkName, ulInode );
                            }
                        }
                    }
                }

                PosixLeave();
            }

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


/** @brief Close a file descriptor.
 *
 *  @param iFildes  The file descriptor to close.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBADF: @p iFildes is not a valid file descriptor.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
    int32_t red_close( int32_t iFildes )
    {
        REDSTATUS ret;

        ret = PosixEnter();

        if( ret == 0 )
        {
            ret = FildesClose( iFildes );

            PosixLeave();
        }

        return PosixReturn( ret );
    }


/** @brief Read from an open file.
 *
 *  The read takes place at the file offset associated with @p iFildes and
 *  advances the file offset by the number of bytes actually read.
 *
 *  Data which has not yet been written, but which is before the end-of-file
 *  (sparse data), will 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.
 *
 *  @param iFildes  The file descriptor from which to read.
 *  @param pBuffer  The buffer to populate with data read.  Must be at least
 *                  @p ulLength bytes in size.
 *  @param ulLength Number of bytes to attempt to read.
 *
 *  @return On success, returns a nonnegative value indicating the number of
 *          bytes actually read.  On error, -1 is returned and #red_errno is
 *          set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBADF: The @p iFildes argument is not a valid file descriptor open
 *    for reading.
 *  - #RED_EINVAL: @p pBuffer is `NULL`; or @p ulLength exceeds INT32_MAX and
 *    cannot be returned properly.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_EISDIR: The @p iFildes is a file descriptor for a directory.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
    int32_t red_read( int32_t iFildes,
                      void * pBuffer,
                      uint32_t ulLength )
    {
        uint32_t ulLenRead = 0U;
        REDSTATUS ret;
        int32_t iReturn;

        if( ulLength > ( uint32_t ) INT32_MAX )
        {
            ret = -RED_EINVAL;
        }
        else
        {
            ret = PosixEnter();
        }

        if( ret == 0 )
        {
            REDHANDLE * pHandle;

            ret = FildesToHandle( iFildes, FTYPE_FILE, &pHandle );

            if( ( ret == 0 ) && ( ( pHandle->bFlags & HFLAG_READABLE ) == 0U ) )
            {
                ret = -RED_EBADF;
            }

            #if REDCONF_VOLUME_COUNT > 1U
                if( ret == 0 )
                {
                    ret = RedCoreVolSetCurrent( pHandle->bVolNum );
                }
            #endif

            if( ret == 0 )
            {
                ulLenRead = ulLength;
                ret = RedCoreFileRead( pHandle->ulInode, pHandle->ullOffset, &ulLenRead, pBuffer );
            }

            if( ret == 0 )
            {
                REDASSERT( ulLenRead <= ulLength );

                pHandle->ullOffset += ulLenRead;
            }

            PosixLeave();
        }

        if( ret == 0 )
        {
            iReturn = ( int32_t ) ulLenRead;
        }
        else
        {
            iReturn = PosixReturn( ret );
        }

        return iReturn;
    }


    #if REDCONF_READ_ONLY == 0

/** @brief Write to an open file.
 *
 *  The write takes place at the file offset associated with @p iFildes and
 *  advances the file offset by the number of bytes actually written.
 *  Alternatively, if @p iFildes was opened with #RED_O_APPEND, the file offset
 *  is set to the end-of-file before the write begins, and likewise advances by
 *  the number of bytes actually written.
 *
 *  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 (-1), 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 iFildes  The file descriptor to write to.
 *  @param pBuffer  The buffer containing the data to be written.  Must be at
 *                  least @p ulLength bytes in size.
 *  @param ulLength Number of bytes to attempt to write.
 *
 *  @return On success, returns a nonnegative value indicating the number of
 *          bytes actually written.  On error, -1 is returned and #red_errno is
 *          set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBADF: The @p iFildes argument is not a valid file descriptor open
 *    for writing.  This includes the case where the file descriptor is for a
 *    directory.
 *  - #RED_EFBIG: No data can be written to the current file offset since the
 *    resulting file size would exceed the maximum file size.
 *  - #RED_EINVAL: @p pBuffer is `NULL`; or @p ulLength exceeds INT32_MAX and
 *    cannot be returned properly.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_ENOSPC: No data can be written because there is insufficient free
 *    space.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_write( int32_t iFildes,
                           const void * pBuffer,
                           uint32_t ulLength )
        {
            uint32_t ulLenWrote = 0U;
            REDSTATUS ret;
            int32_t iReturn;

            if( ulLength > ( uint32_t ) INT32_MAX )
            {
                ret = -RED_EINVAL;
            }
            else
            {
                ret = PosixEnter();
            }

            if( ret == 0 )
            {
                REDHANDLE * pHandle;

                ret = FildesToHandle( iFildes, FTYPE_FILE, &pHandle );

                if( ret == -RED_EISDIR )
                {
                    /*  POSIX says that if a file descriptor is not writable, the
                     *  errno should be -RED_EBADF.  Directory file descriptors are
                     *  never writable, and unlike for read(), the spec does not
                     *  list -RED_EISDIR as an allowed errno.  Therefore -RED_EBADF
                     *  takes precedence.
                     */
                    ret = -RED_EBADF;
                }

                if( ( ret == 0 ) && ( ( pHandle->bFlags & HFLAG_WRITEABLE ) == 0U ) )
                {
                    ret = -RED_EBADF;
                }

                #if REDCONF_VOLUME_COUNT > 1U
                    if( ret == 0 )
                    {
                        ret = RedCoreVolSetCurrent( pHandle->bVolNum );
                    }
                #endif

                if( ( ret == 0 ) && ( ( pHandle->bFlags & HFLAG_APPENDING ) != 0U ) )
                {
                    REDSTAT s;

                    ret = RedCoreStat( pHandle->ulInode, &s );

                    if( ret == 0 )
                    {
                        pHandle->ullOffset = s.st_size;
                    }
                }

                if( ret == 0 )
                {
                    ulLenWrote = ulLength;
                    ret = RedCoreFileWrite( pHandle->ulInode, pHandle->ullOffset, &ulLenWrote, pBuffer );
                }

                if( ret == 0 )
                {
                    REDASSERT( ulLenWrote <= ulLength );

                    pHandle->ullOffset += ulLenWrote;
                }

                PosixLeave();
            }

            if( ret == 0 )
            {
                iReturn = ( int32_t ) ulLenWrote;
            }
            else
            {
                iReturn = PosixReturn( ret );
            }

            return iReturn;
        }
    #endif /* if REDCONF_READ_ONLY == 0 */


    #if REDCONF_READ_ONLY == 0

/** @brief Synchronizes changes to a file.
 *
 *  Commits all changes associated with a file or directory (including file
 *  data, directory contents, and metadata) to permanent storage.  This
 *  function will not return until the operation is complete.
 *
 *  In the current implementation, this function has global effect.  All dirty
 *  buffers are flushed and a transaction point is committed.  Fsyncing one
 *  file effectively fsyncs all files.
 *
 *  If fsync automatic transactions have been disabled, this function does
 *  nothing and returns success.  In the current implementation, this is the
 *  only real difference between this function and red_transact(): this
 *  function can be configured to do nothing, whereas red_transact() is
 *  unconditional.
 *
 *  Applications written for portability should avoid assuming red_fsync()
 *  effects all files, and use red_fsync() on each file that needs to be
 *  synchronized.
 *
 *  Passing read-only file descriptors to this function is permitted.
 *
 *  @param iFildes  The file descriptor to synchronize.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBADF: The @p iFildes argument is not a valid file descriptor.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_fsync( int32_t iFildes )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                REDHANDLE * pHandle;

                ret = FildesToHandle( iFildes, FTYPE_EITHER, &pHandle );

                #if REDCONF_VOLUME_COUNT > 1U
                    if( ret == 0 )
                    {
                        ret = RedCoreVolSetCurrent( pHandle->bVolNum );
                    }
                #endif

                /*  No core event for fsync, so this transaction flag needs to be
                 *  implemented here.
                 */
                if( ret == 0 )
                {
                    uint32_t ulTransMask;

                    ret = RedCoreTransMaskGet( &ulTransMask );

                    if( ( ret == 0 ) && ( ( ulTransMask & RED_TRANSACT_FSYNC ) != 0U ) )
                    {
                        ret = RedCoreVolTransact();
                    }
                }

                PosixLeave();
            }

            return PosixReturn( ret );
        }
    #endif /* if REDCONF_READ_ONLY == 0 */


/** @brief Move the read/write file offset.
 *
 *  The file offset of the @p iFildes file descriptor is set to @p llOffset,
 *  relative to some starting position.  The available positions are:
 *
 *  - ::RED_SEEK_SET Seek from the start of the file.  In other words,
 *    @p llOffset becomes the new file offset.
 *  - ::RED_SEEK_CUR Seek from the current file offset.  In other words,
 *    @p llOffset is added to the current file offset.
 *  - ::RED_SEEK_END Seek from the end-of-file.  In other words, the new file
 *    offset is the file size plus @p llOffset.
 *
 *  Since @p llOffset is signed (can be negative), it is possible to seek
 *  backward with ::RED_SEEK_CUR or ::RED_SEEK_END.
 *
 *  It is permitted to seek beyond the end-of-file; this does not increase the
 *  file size (a subsequent red_write() call would).
 *
 *  Unlike POSIX lseek, this function cannot be used with directory file
 *  descriptors.
 *
 *  @param iFildes  The file descriptor whose offset is to be updated.
 *  @param llOffset The new file offset, relative to @p whence.
 *  @param whence   The location from which @p llOffset should be applied.
 *
 *  @return On success, returns the new file position, measured in bytes from
 *          the beginning of the file.  On error, -1 is returned and #red_errno
 *          is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBADF: The @p iFildes argument is not an open file descriptor.
 *  - #RED_EINVAL: @p whence is not a valid `RED_SEEK_` value; or the resulting
 *    file offset would be negative or beyond the maximum file size.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_EISDIR: The @p iFildes argument is a file descriptor for a directory.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
    int64_t red_lseek( int32_t iFildes,
                       int64_t llOffset,
                       REDWHENCE whence )
    {
        REDSTATUS ret;
        int64_t llReturn = -1; /* Init'd to quiet warnings. */

        ret = PosixEnter();

        if( ret == 0 )
        {
            int64_t llFrom = 0; /* Init'd to quiet warnings. */
            REDHANDLE * pHandle;

            /*  Unlike POSIX, we disallow lseek() on directory handles.
             */
            ret = FildesToHandle( iFildes, FTYPE_FILE, &pHandle );

            #if REDCONF_VOLUME_COUNT > 1U
                if( ret == 0 )
                {
                    ret = RedCoreVolSetCurrent( pHandle->bVolNum );
                }
            #endif

            if( ret == 0 )
            {
                switch( whence )
                {
                    /*  Seek from the beginning of the file.
                     */
                    case RED_SEEK_SET:
                        llFrom = 0;
                        break;

                    /*  Seek from the current file offset.
                     */
                    case RED_SEEK_CUR:
                        REDASSERT( pHandle->ullOffset <= ( uint64_t ) INT64_MAX );
                        llFrom = ( int64_t ) pHandle->ullOffset;
                        break;

                    /*  Seek from the end of the file.
                     */
                    case RED_SEEK_END:
                       {
                           REDSTAT s;

                           ret = RedCoreStat( pHandle->ulInode, &s );

                           if( ret == 0 )
                           {
                               REDASSERT( s.st_size <= ( uint64_t ) INT64_MAX );
                               llFrom = ( int64_t ) s.st_size;
                           }

                           break;
                       }

                    default:
                        ret = -RED_EINVAL;
                        break;
                }
            }

            if( ret == 0 )
            {
                REDASSERT( llFrom >= 0 );

                /*  Avoid signed integer overflow from llFrom + llOffset with large
                 *  values of llOffset and nonzero llFrom values.  Underflow isn't
                 *  possible since llFrom is nonnegative.
                 */
                if( ( llOffset > 0 ) && ( ( ( uint64_t ) llFrom + ( uint64_t ) llOffset ) > ( uint64_t ) INT64_MAX ) )
                {
                    ret = -RED_EINVAL;
                }
                else
                {
                    int64_t llNewOffset = llFrom + llOffset;

                    if( ( llNewOffset < 0 ) || ( ( uint64_t ) llNewOffset > gpRedVolume->ullMaxInodeSize ) )
                    {
                        /*  Invalid file offset.
                         */
                        ret = -RED_EINVAL;
                    }
                    else
                    {
                        pHandle->ullOffset = ( uint64_t ) llNewOffset;
                        llReturn = llNewOffset;
                    }
                }
            }

            PosixLeave();
        }

        if( ret != 0 )
        {
            llReturn = PosixReturn( ret );
        }

        return llReturn;
    }


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

/** @brief Truncate a file to a specified length.
 *
 *  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).
 *
 *  The value of the file offset is not modified by this function.
 *
 *  Unlike POSIX ftruncate, this function can fail when the disk is full if
 *  @p ullSize is non-zero.  If decreasing the file size, this can be fixed by
 *  transacting and trying again: Reliance Edge guarantees that it is possible
 *  to perform a truncate of at least one file that decreases the file size
 *  after a transaction point.  If disk full transactions are enabled, this will
 *  happen automatically.
 *
 *  @param iFildes  The file descriptor of the file to truncate.
 *  @param ullSize  The new size of the file.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBADF: The @p iFildes argument is not a valid file descriptor open
 *    for writing.  This includes the case where the file descriptor is for a
 *    directory.
 *  - #RED_EFBIG: @p ullSize exceeds the maximum file size.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_ENOSPC: Insufficient free space to perform the truncate.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_ftruncate( int32_t iFildes,
                               uint64_t ullSize )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                REDHANDLE * pHandle;

                ret = FildesToHandle( iFildes, FTYPE_FILE, &pHandle );

                if( ret == -RED_EISDIR )
                {
                    /*  Similar to red_write() (see comment there), the RED_EBADF error
                     *  for a non-writable file descriptor takes precedence.
                     */
                    ret = -RED_EBADF;
                }

                if( ( ret == 0 ) && ( ( pHandle->bFlags & HFLAG_WRITEABLE ) == 0U ) )
                {
                    ret = -RED_EBADF;
                }

                #if REDCONF_VOLUME_COUNT > 1U
                    if( ret == 0 )
                    {
                        ret = RedCoreVolSetCurrent( pHandle->bVolNum );
                    }
                #endif

                if( ret == 0 )
                {
                    ret = RedCoreFileTruncate( pHandle->ulInode, ullSize );
                }

                PosixLeave();
            }

            return PosixReturn( ret );
        }
    #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_FTRUNCATE == 1 ) */


/** @brief Get the status of a file or directory.
 *
 *  See the ::REDSTAT type for the details of the information returned.
 *
 *  @param iFildes  An open file descriptor for the file whose information is
 *                  to be retrieved.
 *  @param pStat    Pointer to a ::REDSTAT buffer to populate.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBADF: The @p iFildes argument is not a valid file descriptor.
 *  - #RED_EINVAL: @p pStat is `NULL`.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
    int32_t red_fstat( int32_t iFildes,
                       REDSTAT * pStat )
    {
        REDSTATUS ret;

        ret = PosixEnter();

        if( ret == 0 )
        {
            REDHANDLE * pHandle;

            ret = FildesToHandle( iFildes, FTYPE_EITHER, &pHandle );

            #if REDCONF_VOLUME_COUNT > 1U
                if( ret == 0 )
                {
                    ret = RedCoreVolSetCurrent( pHandle->bVolNum );
                }
            #endif

            if( ret == 0 )
            {
                ret = RedCoreStat( pHandle->ulInode, pStat );
            }

            PosixLeave();
        }

        return PosixReturn( ret );
    }


    #if REDCONF_API_POSIX_READDIR == 1

/** @brief Open a directory stream for reading.
 *
 *  @param pszPath  The path of the directory to open.
 *
 *  @return On success, returns a pointer to a ::REDDIR object that can be used
 *          with red_readdir() and red_closedir().  On error, returns `NULL`
 *          and #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EINVAL: @p pszPath is `NULL`; or the volume containing the path is
 *    not mounted.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_ENOENT: A component of @p pszPath does not exist; or the @p pszPath
 *    argument, after removing the volume prefix, points to an empty string.
 *  - #RED_ENOTDIR: A component of @p pszPath is a not a directory.
 *  - #RED_EMFILE: There are no available file descriptors.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        REDDIR * red_opendir( const char * pszPath )
        {
            int32_t iFildes;
            REDSTATUS ret;
            REDDIR * pDir = NULL;

            ret = PosixEnter();

            if( ret == 0 )
            {
                ret = FildesOpen( pszPath, RED_O_RDONLY, FTYPE_DIR, &iFildes );

                if( ret == 0 )
                {
                    uint16_t uHandleIdx;

                    FildesUnpack( iFildes, &uHandleIdx, NULL, NULL );
                    pDir = &gaHandle[ uHandleIdx ];
                }

                PosixLeave();
            }

            REDASSERT( ( pDir == NULL ) == ( ret != 0 ) );

            if( pDir == NULL )
            {
                red_errno = -ret;
            }

            return pDir;
        }


/** @brief Read from a directory stream.
 *
 *  The ::REDDIRENT pointer returned by this function will be overwritten by
 *  subsequent calls on the same @p pDir.  Calls with other ::REDDIR objects
 *  will *not* modify the returned ::REDDIRENT.
 *
 *  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.
 *
 *  This function (like its POSIX equivalent) returns `NULL` in two cases: on
 *  error and when the end of the directory is reached.  To distinguish between
 *  these two cases, the application should set #red_errno to zero before
 *  calling this function, and if `NULL` is returned, check if #red_errno is
 *  still zero.  If it is, the end of the directory was reached; otherwise,
 *  there was an error.
 *
 *  @param pDirStream   The directory stream to read from.
 *
 *  @return On success, returns a pointer to a ::REDDIRENT object which is
 *          populated with directory entry information read from the directory.
 *          On error, returns `NULL` and #red_errno is set appropriately.  If at
 *          the end of the directory, returns `NULL` but #red_errno is not
 *          modified.
 *
 *  <b>Errno values</b>
 *  - #RED_EBADF: @p pDirStream is not an open directory stream.
 *  - #RED_EIO: A disk I/O error occurred.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        REDDIRENT * red_readdir( REDDIR * pDirStream )
        {
            REDSTATUS ret;
            REDDIRENT * pDirEnt = NULL;

            ret = PosixEnter();

            if( ret == 0 )
            {
                if( !DirStreamIsValid( pDirStream ) )
                {
                    ret = -RED_EBADF;
                }

                #if REDCONF_VOLUME_COUNT > 1U
                    else
                    {
                        ret = RedCoreVolSetCurrent( pDirStream->bVolNum );
                    }
                #endif

                if( ret == 0 )
                {
                    uint32_t ulDirPosition;

                    /*  To save memory, the directory position is stored in the same
                     *  location as the file offset.  This would be a bit cleaner using
                     *  a union, but MISRA-C:2012 Rule 19.2 disallows unions.
                     */
                    REDASSERT( pDirStream->ullOffset <= UINT32_MAX );
                    ulDirPosition = ( uint32_t ) pDirStream->ullOffset;

                    ret = RedCoreDirRead( pDirStream->ulInode, &ulDirPosition, pDirStream->dirent.d_name, &pDirStream->dirent.d_ino );

                    pDirStream->ullOffset = ulDirPosition;

                    if( ret == 0 )
                    {
                        /*  POSIX extension: return stat information with the dirent.
                         */
                        ret = RedCoreStat( pDirStream->dirent.d_ino, &pDirStream->dirent.d_stat );

                        if( ret == 0 )
                        {
                            pDirEnt = &pDirStream->dirent;
                        }
                    }
                    else if( ret == -RED_ENOENT )
                    {
                        /*  Reached the end of the directory; return NULL but do not set
                         *  errno.
                         */
                        ret = 0;
                    }
                    else
                    {
                        /*  Miscellaneous error; return NULL and set errno (done below).
                         */
                    }
                }

                PosixLeave();
            }

            if( ret != 0 )
            {
                REDASSERT( pDirEnt == NULL );

                red_errno = -ret;
            }

            return pDirEnt;
        }


/** @brief Rewind a directory stream to read it from the beginning.
 *
 *  Similar to closing the directory object and opening it again, but without
 *  the need for the path.
 *
 *  Since this function (like its POSIX equivalent) cannot return an error,
 *  it takes no action in error conditions, such as when @p pDirStream is
 *  invalid.
 *
 *  @param pDirStream   The directory stream to rewind.
 */
        void red_rewinddir( REDDIR * pDirStream )
        {
            if( PosixEnter() == 0 )
            {
                if( DirStreamIsValid( pDirStream ) )
                {
                    pDirStream->ullOffset = 0U;
                }

                PosixLeave();
            }
        }


/** @brief Close a directory stream.
 *
 *  After calling this function, @p pDirStream should no longer be used.
 *
 *  @param pDirStream   The directory stream to close.
 *
 *  @return On success, zero is returned.  On error, -1 is returned and
 #red_errno is set appropriately.
 *
 *  <b>Errno values</b>
 *  - #RED_EBADF: @p pDirStream is not an open directory stream.
 *  - #RED_EUSERS: Cannot become a file system user: too many users.
 */
        int32_t red_closedir( REDDIR * pDirStream )
        {
            REDSTATUS ret;

            ret = PosixEnter();

            if( ret == 0 )
            {
                if( DirStreamIsValid( pDirStream ) )
                {
                    /*  Mark this handle as unused.
                     */
                    pDirStream->ulInode = INODE_INVALID;
                }
                else
                {
                    ret = -RED_EBADF;
                }

                PosixLeave();
            }

            return PosixReturn( ret );
        }
    #endif /* REDCONF_API_POSIX_READDIR */


/** @brief Pointer to where the last file system error (errno) is stored.
 *
 *  This function is intended to be used via the #red_errno macro, or a similar
 *  user-defined macro, that can be used both as an lvalue (writable) and an
 *  rvalue (readable).
 *
 *  Under normal circumstances, the errno for each task is stored in a
 *  different location.  Applications do not need to worry about one task
 *  obliterating an error value that another task needed to read.  This task
 *  errno for is initially zero.  When one of the POSIX-like APIs returns an
 *  indication of error, the location for the calling task will be populated
 *  with the error value.
 *
 *  In some circumstances, this function will return a pointer to a global
 *  errno location which is shared by multiple tasks.  If the calling task is
 *  not registered as a file system user and all of the task slots are full,
 *  there can be no task-specific errno, so the global pointer is returned.
 *  Likewise, if the file system driver is uninitialized, there are no
 *  registered file system users and this function always returns the pointer
 *  to the global errno.  Under these circumstances, multiple tasks
 *  manipulating errno could be problematic.
 *
 *  This function never returns `NULL` under any circumstances.  The #red_errno
 *  macro unconditionally dereferences the return value from this function, so
 *  returning `NULL` could result in a fault.
 *
 *  @return Pointer to where the errno value is stored for this task.
 */
    REDSTATUS * red_errnoptr( void )
    {
        /*  The global errno value, used in single-task configurations and when the
         *  caller is not (and cannot become) a file system user (which includes
         *  when the driver is uninitialized).
         */
        static REDSTATUS iGlobalErrno = 0;

        #if REDCONF_TASK_COUNT == 1U
            return &iGlobalErrno;
        #else
            REDSTATUS * piErrno;

            if( gfPosixInited )
            {
                uint32_t ulTaskId = RedOsTaskId();
                uint32_t ulIdx;

                REDASSERT( ulTaskId != 0U );

                /*  If this task has used the file system before, it will already have
                 *  a task slot, which includes the task-specific errno.
                 */
                RedOsMutexAcquire();

                for( ulIdx = 0U; ulIdx < REDCONF_TASK_COUNT; ulIdx++ )
                {
                    if( gaTask[ ulIdx ].ulTaskId == ulTaskId )
                    {
                        break;
                    }
                }

                RedOsMutexRelease();

                if( ulIdx == REDCONF_TASK_COUNT )
                {
                    REDSTATUS ret;

                    /*  This task is not a file system user, so try to register it as
                     *  one.  This FS mutex must be held in order to register.
                     */
                    RedOsMutexAcquire();

                    ret = TaskRegister( &ulIdx );

                    RedOsMutexRelease();

                    if( ret == 0 )
                    {
                        REDASSERT( gaTask[ ulIdx ].ulTaskId == RedOsTaskId() );
                        REDASSERT( gaTask[ ulIdx ].iErrno == 0 );

                        piErrno = &gaTask[ ulIdx ].iErrno;
                    }
                    else
                    {
                        /*  Unable to register; use the global errno.
                         */
                        piErrno = &iGlobalErrno;
                    }
                }
                else
                {
                    piErrno = &gaTask[ ulIdx ].iErrno;
                }
            }
            else
            {
                /*  There are no registered file system tasks when the driver is
                 *  uninitialized, so use the global errno.
                 */
                piErrno = &iGlobalErrno;
            }

            /*  This function is not allowed to return NULL.
             */
            REDASSERT( piErrno != NULL );
            return piErrno;
        #endif /* if REDCONF_TASK_COUNT == 1U */
    }
/** @} */

/*-------------------------------------------------------------------
 *   Helper Functions
 *  -------------------------------------------------------------------*/

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

/** @brief Remove a link to a file or directory.
 *
 *  If the link count becomes zero, the file or directory is deleted.
 *
 *  @param pszPath  Path of the link to remove.
 *  @param type     The expected type of the path: file, directory, or either.
 *                  An error is returned if the expected type is file or
 *                  directory and does not match the path.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval -RED_EBUSY          @p pszPath points to an inode with open handles
 *                              and a link count of one.
 *  @retval -RED_EINVAL         @p pszPath is `NULL`; or the volume containing
 *                              the path is not mounted.
 *  @retval -RED_EIO            A disk I/O error occurred.
 *  @retval -RED_EISDIR         @p type is ::FTYPE_FILE and the path names a
 *                              directory.
 *  @retval -RED_ENAMETOOLONG   @p pszName is too long.
 *  @retval -RED_ENOENT         The path does not name an existing file; or
 *                              @p pszPath, after removing the volume prefix,
 *                              points to an empty string.
 *  @retval -RED_ENOTDIR        @p type is ::FTYPE_DIR and the path does not
 *                              name a directory.
 *  @retval -RED_ENOTEMPTY      @p pszPath is a directory which is not empty.
 *  @retval -RED_ENOSPC         The file system does not have enough space to
 *                              modify the parent directory to perform the
 *                              deletion.
 */
        static REDSTATUS UnlinkSub( const char * pszPath,
                                    FTYPE type )
        {
            uint8_t bVolNum;
            const char * pszLocalPath;
            REDSTATUS ret;

            ret = RedPathSplit( pszPath, &bVolNum, &pszLocalPath );

            #if REDCONF_VOLUME_COUNT > 1U
                if( ret == 0 )
                {
                    ret = RedCoreVolSetCurrent( bVolNum );
                }
            #endif

            if( ret == 0 )
            {
                const char * pszName;
                uint32_t ulPInode;

                ret = RedPathToName( pszLocalPath, &ulPInode, &pszName );

                if( ret == 0 )
                {
                    uint32_t ulInode;

                    ret = RedCoreLookup( ulPInode, pszName, &ulInode );

                    /*  ModeTypeCheck() always passes when the type is FTYPE_EITHER, so
                     *  skip stat'ing the inode in that case.
                     */
                    if( ( ret == 0 ) && ( type != FTYPE_EITHER ) )
                    {
                        REDSTAT InodeStat;

                        ret = RedCoreStat( ulInode, &InodeStat );

                        if( ret == 0 )
                        {
                            ret = ModeTypeCheck( InodeStat.st_mode, type );
                        }
                    }

                    if( ret == 0 )
                    {
                        ret = InodeUnlinkCheck( ulInode );
                    }

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

            return ret;
        }
    #endif /* (REDCONF_API_POSIX_UNLINK == 1) || (REDCONF_API_POSIX_RMDIR == 1) */


/** @brief Get a file descriptor for a path.
 *
 *  @param pszPath      Path to a file to open.
 *  @param ulOpenMode   The RED_O_* flags the descriptor is opened with.
 *  @param type         Indicates the expected descriptor type: file, directory,
 *                      or either.
 *  @param piFildes     On successful return, populated with the file
 *                      descriptor.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0                   Operation was successful.
 *  @retval -RED_EINVAL         @p piFildes is `NULL`; or @p pszPath is `NULL`;
 *                              or the volume is not mounted.
 *  @retval -RED_EMFILE         There are no available handles.
 *  @retval -RED_EEXIST         Using #RED_O_CREAT and #RED_O_EXCL, and the
 *                              indicated path already exists.
 *  @retval -RED_EISDIR         The path names a directory and @p ulOpenMode
 *                              includes #RED_O_WRONLY or #RED_O_RDWR.
 *  @retval -RED_ENOENT         #RED_O_CREAT is not set and the named file does
 *                              not exist; or #RED_O_CREAT is set and the parent
 *                              directory does not exist; or the volume does not
 *                              exist; or the @p pszPath argument, after
 *                              removing the volume prefix, points to an empty
 *                              string.
 *  @retval -RED_EIO            A disk I/O error occurred.
 *  @retval -RED_ENAMETOOLONG   The length of a component of @p pszPath is
 *                              longer than #REDCONF_NAME_MAX.
 *  @retval -RED_ENFILE         Attempting to create a file but the file system
 *                              has used all available inode slots.
 *  @retval -RED_ENOSPC         The file does not exist and #RED_O_CREAT was
 *                              specified, but there is insufficient free space
 *                              to expand the directory or to create the new
 *                              file.
 *  @retval -RED_ENOTDIR        A component of the prefix in @p pszPath does not
 *                              name a directory.
 *  @retval -RED_EROFS          The path resides on a read-only file system and
 *                              a write operation was requested.
 */
    static REDSTATUS FildesOpen( const char * pszPath,
                                 uint32_t ulOpenMode,
                                 FTYPE type,
                                 int32_t * piFildes )
    {
        uint8_t bVolNum;
        const char * pszLocalPath;
        REDSTATUS ret;

        ret = RedPathSplit( pszPath, &bVolNum, &pszLocalPath );

        if( ret == 0 )
        {
            if( piFildes == NULL )
            {
                ret = -RED_EINVAL;
            }

            #if REDCONF_READ_ONLY == 0
                else if( gaRedVolume[ bVolNum ].fReadOnly && ( ulOpenMode != RED_O_RDONLY ) )
                {
                    ret = -RED_EROFS;
                }
            #endif
            else
            {
                uint16_t uHandleIdx;
                REDHANDLE * pHandle = NULL;

                /*  Search for an unused handle.
                 */
                for( uHandleIdx = 0U; uHandleIdx < REDCONF_HANDLE_COUNT; uHandleIdx++ )
                {
                    if( gaHandle[ uHandleIdx ].ulInode == INODE_INVALID )
                    {
                        pHandle = &gaHandle[ uHandleIdx ];
                        break;
                    }
                }

                /*  Error if all the handles are in use.
                 */
                if( pHandle == NULL )
                {
                    ret = -RED_EMFILE;
                }
                else
                {
                    bool fCreated = false;
                    uint16_t uMode = 0U;
                    uint32_t ulInode = 0U; /* Init'd to quiet warnings. */

                    #if REDCONF_VOLUME_COUNT > 1U
                        ret = RedCoreVolSetCurrent( bVolNum );

                        if( ret == 0 )
                    #endif
                    {
                        #if REDCONF_READ_ONLY == 0
                            if( ( ulOpenMode & RED_O_CREAT ) != 0U )
                            {
                                uint32_t ulPInode;
                                const char * pszName;

                                ret = RedPathToName( pszLocalPath, &ulPInode, &pszName );

                                if( ret == 0 )
                                {
                                    ret = RedCoreCreate( ulPInode, pszName, false, &ulInode );

                                    if( ret == 0 )
                                    {
                                        fCreated = true;
                                    }
                                    else if( ( ret == -RED_EEXIST ) && ( ( ulOpenMode & RED_O_EXCL ) == 0U ) )
                                    {
                                        /*  If the path already exists and that's OK,
                                         *  lookup its inode number.
                                         */
                                        ret = RedCoreLookup( ulPInode, pszName, &ulInode );
                                    }
                                    else
                                    {
                                        /*  No action, just propagate the error.
                                         */
                                    }
                                }
                            }
                            else
                        #endif /* if REDCONF_READ_ONLY == 0 */
                        {
                            ret = RedPathLookup( pszLocalPath, &ulInode );
                        }
                    }

                    /*  If we created the inode, none of the below stuff is
                     *  necessary.  This is important from an error handling
                     *  perspective -- we do not need code to delete the created
                     *  inode on error.
                     */
                    if( !fCreated )
                    {
                        if( ret == 0 )
                        {
                            REDSTAT s;

                            ret = RedCoreStat( ulInode, &s );

                            if( ret == 0 )
                            {
                                uMode = s.st_mode;
                            }
                        }

                        /*  Error if the inode is not of the expected type.
                         */
                        if( ret == 0 )
                        {
                            ret = ModeTypeCheck( uMode, type );
                        }

                        /*  Directories must always be opened with O_RDONLY.
                         */
                        if( ( ret == 0 ) && RED_S_ISDIR( uMode ) && ( ( ulOpenMode & RED_O_RDONLY ) == 0U ) )
                        {
                            ret = -RED_EISDIR;
                        }

                        #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_FTRUNCATE == 1 )
                            if( ( ret == 0 ) && ( ( ulOpenMode & RED_O_TRUNC ) != 0U ) )
                            {
                                ret = RedCoreFileTruncate( ulInode, UINT64_SUFFIX( 0 ) );
                            }
                        #endif
                    }

                    if( ret == 0 )
                    {
                        int32_t iFildes;

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

                        /*  Populate this handle, marking it as in use.
                         */
                        pHandle->ulInode = ulInode;
                        pHandle->bVolNum = bVolNum;

                        if( RED_S_ISDIR( uMode ) )
                        {
                            pHandle->bFlags |= HFLAG_DIRECTORY;
                        }

                        if( ( ( ulOpenMode & RED_O_RDONLY ) != 0U ) || ( ( ulOpenMode & RED_O_RDWR ) != 0U ) )
                        {
                            pHandle->bFlags |= HFLAG_READABLE;
                        }

                        #if REDCONF_READ_ONLY == 0
                            if( ( ( ulOpenMode & RED_O_WRONLY ) != 0U ) || ( ( ulOpenMode & RED_O_RDWR ) != 0U ) )
                            {
                                pHandle->bFlags |= HFLAG_WRITEABLE;
                            }

                            if( ( ulOpenMode & RED_O_APPEND ) != 0U )
                            {
                                pHandle->bFlags |= HFLAG_APPENDING;
                            }
                        #endif

                        iFildes = FildesPack( uHandleIdx, bVolNum );

                        if( iFildes == -1 )
                        {
                            /*  It should be impossible to get here, unless there
                             *  is memory corruption.
                             */
                            REDERROR();
                            ret = -RED_EFUBAR;
                        }
                        else
                        {
                            *piFildes = iFildes;
                        }
                    }
                }
            }
        }

        return ret;
    }


/** @brief Close a file descriptor.
 *
 *  @param iFildes  The file descriptor to close.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EBADF  @p iFildes is not a valid file descriptor.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
    static REDSTATUS FildesClose( int32_t iFildes )
    {
        REDHANDLE * pHandle;
        REDSTATUS ret;

        ret = FildesToHandle( iFildes, FTYPE_EITHER, &pHandle );

        #if REDCONF_READ_ONLY == 0
            #if REDCONF_VOLUME_COUNT > 1U
                if( ret == 0 )
                {
                    ret = RedCoreVolSetCurrent( pHandle->bVolNum );
                }
            #endif

            /*  No core event for close, so this transaction flag needs to be
             *  implemented here.
             */
            if( ret == 0 )
            {
                uint32_t ulTransMask;

                ret = RedCoreTransMaskGet( &ulTransMask );

                if( ( ret == 0 ) && ( ( ulTransMask & RED_TRANSACT_CLOSE ) != 0U ) )
                {
                    ret = RedCoreVolTransact();
                }
            }
        #endif /* if REDCONF_READ_ONLY == 0 */

        if( ret == 0 )
        {
            /*  Mark this handle as unused.
             */
            pHandle->ulInode = INODE_INVALID;
        }

        return ret;
    }


/** @brief Convert a file descriptor into a handle pointer.
 *
 *  Also validates the file descriptor.
 *
 *  @param iFildes  The file descriptor for which to get a handle.
 *  @param expectedType The expected type of the file descriptor: ::FTYPE_DIR,
 *                      ::FTYPE_FILE, or ::FTYPE_EITHER.
 *  @param ppHandle     On successful return, populated with a pointer to the
 *                      handle associated with @p iFildes.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0               Operation was successful.
 *  @retval -RED_EBADF      @p iFildes is not a valid file descriptor.
 *  @retval -RED_EINVAL     @p ppHandle is `NULL`.
 *  @retval -RED_EISDIR     Expected a file, but the file descriptor is for a
 *                          directory.
 *  @retval -RED_ENOTDIR    Expected a directory, but the file descriptor is for
 *                          a file.
 */
    static REDSTATUS FildesToHandle( int32_t iFildes,
                                     FTYPE expectedType,
                                     REDHANDLE ** ppHandle )
    {
        REDSTATUS ret;

        if( ppHandle == NULL )
        {
            REDERROR();
            ret = -RED_EINVAL;
        }
        else if( iFildes < FD_MIN )
        {
            ret = -RED_EBADF;
        }
        else
        {
            uint16_t uHandleIdx;
            uint8_t bVolNum;
            uint16_t uGeneration;

            FildesUnpack( iFildes, &uHandleIdx, &bVolNum, &uGeneration );

            if( ( uHandleIdx >= REDCONF_HANDLE_COUNT ) ||
                ( bVolNum >= REDCONF_VOLUME_COUNT ) ||
                ( gaHandle[ uHandleIdx ].ulInode == INODE_INVALID ) ||
                ( gaHandle[ uHandleIdx ].bVolNum != bVolNum ) ||
                ( gauGeneration[ bVolNum ] != uGeneration ) )
            {
                ret = -RED_EBADF;
            }
            else if( ( expectedType == FTYPE_FILE ) && ( ( gaHandle[ uHandleIdx ].bFlags & HFLAG_DIRECTORY ) != 0U ) )
            {
                ret = -RED_EISDIR;
            }
            else if( ( expectedType == FTYPE_DIR ) && ( ( gaHandle[ uHandleIdx ].bFlags & HFLAG_DIRECTORY ) == 0U ) )
            {
                ret = -RED_ENOTDIR;
            }
            else
            {
                *ppHandle = &gaHandle[ uHandleIdx ];
                ret = 0;
            }
        }

        return ret;
    }


/** @brief Pack a file descriptor.
 *
 *  @param uHandleIdx   The index of the file handle that will be associated
 *                      with this file descriptor.
 *  @param bVolNum      The volume which contains the file or directory this
 *                      file descriptor was opened against.
 *
 *  @return The packed file descriptor.
 */
    static int32_t FildesPack( uint16_t uHandleIdx,
                               uint8_t bVolNum )
    {
        int32_t iFildes;

        if( ( uHandleIdx >= REDCONF_HANDLE_COUNT ) || ( bVolNum >= REDCONF_VOLUME_COUNT ) )
        {
            REDERROR();
            iFildes = -1;
        }
        else
        {
            uint32_t ulFdBits;

            REDASSERT( gauGeneration[ bVolNum ] <= FD_GEN_MAX );
            REDASSERT( gauGeneration[ bVolNum ] != 0U );

            ulFdBits = gauGeneration[ bVolNum ];
            ulFdBits <<= FD_VOL_BITS;
            ulFdBits |= bVolNum;
            ulFdBits <<= FD_IDX_BITS;
            ulFdBits |= uHandleIdx;

            iFildes = ( int32_t ) ulFdBits;

            if( iFildes < FD_MIN )
            {
                REDERROR();
                iFildes = -1;
            }
        }

        return iFildes;
    }


/** @brief Unpack a file descriptor.
 *
 *  @param iFildes      The file descriptor to unpack.
 *  @param puHandleIdx  If non-NULL, populated with the handle index extracted
 *                      from the file descriptor.
 *  @param pbVolNum     If non-NULL, populated with the volume number extracted
 *                      from the file descriptor.
 *  @param puGeneration If non-NULL, populated with the generation number
 *                      extracted from the file descriptor.
 */
    static void FildesUnpack( int32_t iFildes,
                              uint16_t * puHandleIdx,
                              uint8_t * pbVolNum,
                              uint16_t * puGeneration )
    {
        uint32_t ulFdBits = ( uint32_t ) iFildes;

        REDASSERT( iFildes >= FD_MIN );

        if( puHandleIdx != NULL )
        {
            *puHandleIdx = ( uint16_t ) ( ulFdBits & FD_IDX_MAX );
        }

        ulFdBits >>= FD_IDX_BITS;

        if( pbVolNum != NULL )
        {
            *pbVolNum = ( uint8_t ) ( ulFdBits & FD_VOL_MAX );
        }

        ulFdBits >>= FD_VOL_BITS;

        if( puGeneration != NULL )
        {
            *puGeneration = ( uint16_t ) ( ulFdBits & FD_GEN_MAX );
        }
    }


    #if REDCONF_API_POSIX_READDIR == 1

/** @brief Validate a directory stream object.
 *
 *  @param pDirStream   The directory stream to validate.
 *
 *  @return Whether the directory stream is valid.
 *
 *  @retval true    The directory stream object appears valid.
 *  @retval false   The directory stream object is invalid.
 */
        static bool DirStreamIsValid( const REDDIR * pDirStream )
        {
            bool fRet = true;

            if( pDirStream == NULL )
            {
                fRet = false;
            }
            else
            {
                uint16_t uHandleIdx;

                /*  pDirStream should be a pointer to one of the handles.
                 *
                 *  A good compiler will optimize this loop into a bounds check and an
                 *  alignment check.
                 */
                for( uHandleIdx = 0U; uHandleIdx < REDCONF_HANDLE_COUNT; uHandleIdx++ )
                {
                    if( pDirStream == &gaHandle[ uHandleIdx ] )
                    {
                        break;
                    }
                }

                if( uHandleIdx < REDCONF_HANDLE_COUNT )
                {
                    /*  The handle must be in use, have a valid volume number, and be a
                     *  directory handle.
                     */
                    if( ( pDirStream->ulInode == INODE_INVALID ) ||
                        ( pDirStream->bVolNum >= REDCONF_VOLUME_COUNT ) ||
                        ( ( pDirStream->bFlags & HFLAG_DIRECTORY ) == 0U ) )
                    {
                        fRet = false;
                    }
                }
                else
                {
                    /*  pDirStream is a non-null pointer, but it is not a pointer to one
                     *  of our handles.
                     */
                    fRet = false;
                }
            }

            return fRet;
        }
    #endif /* if REDCONF_API_POSIX_READDIR == 1 */


/** @brief Enter the file system driver.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EINVAL The file system driver is uninitialized.
 *  @retval -RED_EUSERS Cannot become a file system user: too many users.
 */
    static REDSTATUS PosixEnter( void )
    {
        REDSTATUS ret;

        if( gfPosixInited )
        {
            #if REDCONF_TASK_COUNT > 1U
                RedOsMutexAcquire();

                ret = TaskRegister( NULL );

                if( ret != 0 )
                {
                    RedOsMutexRelease();
                }
            #else
                ret = 0;
            #endif
        }
        else
        {
            ret = -RED_EINVAL;
        }

        return ret;
    }


/** @brief Leave the file system driver.
 */
    static void PosixLeave( void )
    {
        /*  If the driver was uninitialized, PosixEnter() should have failed and we
         *  should not be calling PosixLeave().
         */
        REDASSERT( gfPosixInited );

        #if REDCONF_TASK_COUNT > 1U
            RedOsMutexRelease();
        #endif
    }


/** @brief Check that a mode is consistent with the given expected type.
 *
 *  @param uMode        An inode mode, indicating whether the inode is a file
 *                      or a directory.
 *  @param expectedType The expected type: ::FTYPE_FILE, ::FTYPE_DIR, or
 *                      ::FTYPE_EITHER.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0               Operation was successful.
 *  @retval -RED_EISDIR     Expected type is file, actual type is directory.
 *  @retval -RED_ENOTDIR    Expected type is directory, actual type is file.
 */
    static REDSTATUS ModeTypeCheck( uint16_t uMode,
                                    FTYPE expectedType )
    {
        REDSTATUS ret;

        if( ( expectedType == FTYPE_FILE ) && RED_S_ISDIR( uMode ) )
        {
            /*  Expected file, found directory.
             */
            ret = -RED_EISDIR;
        }
        else if( ( expectedType == FTYPE_DIR ) && RED_S_ISREG( uMode ) )
        {
            /*  Expected directory, found file.
             */
            ret = -RED_ENOTDIR;
        }
        else
        {
            /*  No expected type or found what we expected.
             */
            ret = 0;
        }

        return ret;
    }


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

/** @brief Check whether an inode can be unlinked.
 *
 *  If an inode has a link count of 1 (meaning unlinking another name would
 *  result in the deletion of the inode) and open handles, it cannot be deleted
 *  since this would break open handles.
 *
 *  @param ulInode  The inode whose name is to be unlinked.
 *
 *  @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_EBUSY  The inode has a link count of one and open handles.
 *  @retval -RED_EIO    A disk I/O error occurred.
 */
        static REDSTATUS InodeUnlinkCheck( uint32_t ulInode )
        {
            uint16_t uHandleIdx;
            REDSTATUS ret;

            #if REDCONF_API_POSIX_LINK == 0
                ret = 0;
            #else
                REDSTAT InodeStat;

                ret = RedCoreStat( ulInode, &InodeStat );

                /*  We only need to check for open handles if the inode is down to its last
                 *  link.  If it has multiple links, the inode will continue to exist, so
                 *  deleting the name will not break the open handles.
                 */
                if( ( ret == 0 ) && ( InodeStat.st_nlink == 1U ) )
            #endif
            {
                for( uHandleIdx = 0U; uHandleIdx < REDCONF_HANDLE_COUNT; uHandleIdx++ )
                {
                    if( ( gaHandle[ uHandleIdx ].ulInode == ulInode ) && ( gaHandle[ uHandleIdx ].bVolNum == gbRedVolNum ) )
                    {
                        ret = -RED_EBUSY;
                        break;
                    }
                }
            }

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


    #if REDCONF_TASK_COUNT > 1U

/** @brief Register a task as a file system user, if it is not already
 *         registered as one.
 *
 *  The caller must hold the FS mutex.
 *
 *  @param pulTaskIdx   On successful return, if non-NULL, populated with the
 *                      index of the task slot assigned to the calling task.
 *                      This is populated whether or not the task had already
 *                      been registered.
 *
 *  @return A negated ::REDSTATUS code indicating the operation result.
 *
 *  @retval 0           Operation was successful.
 *  @retval -RED_EUSERS Cannot become a file system user: too many users.
 */
        static REDSTATUS TaskRegister( uint32_t * pulTaskIdx )
        {
            uint32_t ulTaskId = RedOsTaskId();
            uint32_t ulFirstFreeIdx = REDCONF_TASK_COUNT;
            uint32_t ulIdx;
            REDSTATUS ret;

            REDASSERT( ulTaskId != 0U );

            /*  Scan the task slots to determine if the task is registered as a file
             *  system task.
             */
            for( ulIdx = 0U; ulIdx < REDCONF_TASK_COUNT; ulIdx++ )
            {
                if( gaTask[ ulIdx ].ulTaskId == ulTaskId )
                {
                    break;
                }

                if( ( ulFirstFreeIdx == REDCONF_TASK_COUNT ) && ( gaTask[ ulIdx ].ulTaskId == 0U ) )
                {
                    ulFirstFreeIdx = ulIdx;
                }
            }

            if( ulIdx == REDCONF_TASK_COUNT )
            {
                /*  Task not already registered.
                 */
                if( ulFirstFreeIdx == REDCONF_TASK_COUNT )
                {
                    /*  Cannot register task, no more slots.
                     */
                    ret = -RED_EUSERS;
                }
                else
                {
                    /*  Registering task.
                     */
                    ulIdx = ulFirstFreeIdx;
                    gaTask[ ulIdx ].ulTaskId = ulTaskId;
                    ret = 0;
                }
            }
            else
            {
                /*  Task already registered.
                 */
                ret = 0;
            }

            if( ( ret == 0 ) && ( pulTaskIdx != NULL ) )
            {
                *pulTaskIdx = ulIdx;
            }

            return ret;
        }
    #endif /* REDCONF_TASK_COUNT > 1U */


/** @brief Convert an error value into a simple 0 or -1 return.
 *
 *  This function is simple, but what it does is needed in many places.  It
 *  returns zero if @p iError is zero (meaning success) or it returns -1 if
 *  @p iError is nonzero (meaning error).  Also, if @p iError is nonzero, it
 *  is saved in red_errno.
 *
 *  @param  iError  The error value.
 *
 *  @return Returns 0 if @p iError is 0; otherwise, returns -1.
 */
    static int32_t PosixReturn( REDSTATUS iError )
    {
        int32_t iReturn;

        if( iError == 0 )
        {
            iReturn = 0;
        }
        else
        {
            iReturn = -1;

            /*  The errors should be negative, and errno positive.
             */
            REDASSERT( iError < 0 );
            red_errno = -iError;
        }

        return iReturn;
    }


#endif /* REDCONF_API_POSIX == 1 */
