blob: 7a21f0696447e8409c207ed9c43ba351ad734c8b [file] [log] [blame]
<
/* ----> 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;