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