3089 lines
97 KiB
C
3089 lines
97 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
|
||
|
/** @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
|
||
|
/** @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
|
||
|
|
||
|
|
||
|
/** @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
|
||
|
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_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_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_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(ret == 0)
|
||
|
{
|
||
|
ret = RedCoreRename(ulOldPInode, pszOldName, ulNewPInode, pszNewName);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PosixLeave();
|
||
|
}
|
||
|
|
||
|
return PosixReturn(ret);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#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
|
||
|
|
||
|
|
||
|
/** @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
|
||
|
/** @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
|
||
|
|
||
|
|
||
|
/** @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
|
||
|
|
||
|
|
||
|
/** @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
|
||
|
}
|
||
|
/** @} */
|
||
|
|
||
|
/*-------------------------------------------------------------------
|
||
|
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
|
||
|
{
|
||
|
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(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
|
||
|
|
||
|
|
||
|
/** @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_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 */
|
||
|
|