/* ----> 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 */ | |