You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3116 lines
107 KiB
C
3116 lines
107 KiB
C
/* ----> 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 */
|