/* ----> 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 #if REDCONF_API_POSIX == 1 /** @defgroup red_group_posix The POSIX-like File System Interface * @{ */ #include #include #include #include /*------------------------------------------------------------------- * 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. * * Errno values * - #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. * * Errno values * - #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. * * Errno values * - #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. * * Errno values * - #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. * * Errno values * - #RED_EBUSY: Volume is mounted. * - #RED_EINVAL: @p pszVolume is `NULL`; or the driver is uninitialized. * - #RED_EIO: I/O error formatting the volume. * - #RED_ENOENT: @p pszVolume is not a valid volume path prefix. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_format( const char * pszVolume ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { uint8_t bVolNum; ret = RedPathSplit( pszVolume, &bVolNum, NULL ); #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( bVolNum ); } #endif if( ret == 0 ) { ret = RedCoreVolFormat(); } PosixLeave(); } return PosixReturn( ret ); } #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_FORMAT == 1 ) */ #if REDCONF_READ_ONLY == 0 /** @brief Commit a transaction point. * * Reliance Edge is a transactional file system. All modifications, of both * metadata and filedata, are initially working state. A transaction point * is a process whereby the working state atomically becomes the committed * state, replacing the previous committed state. Whenever Reliance Edge is * mounted, including after power loss, the state of the file system after * mount is the most recent committed state. Nothing from the committed * state is ever missing, and nothing from the working state is ever included. * * @param pszVolume A path prefix identifying the volume to transact. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #RED_EINVAL: Volume is not mounted; or @p pszVolume is `NULL`. * - #RED_EIO: I/O error during the transaction point. * - #RED_ENOENT: @p pszVolume is not a valid volume path prefix. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_transact( const char * pszVolume ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { uint8_t bVolNum; ret = RedPathSplit( pszVolume, &bVolNum, NULL ); #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( bVolNum ); } #endif if( ret == 0 ) { ret = RedCoreVolTransact(); } PosixLeave(); } return PosixReturn( ret ); } #endif /* if REDCONF_READ_ONLY == 0 */ #if REDCONF_READ_ONLY == 0 /** @brief Update the transaction mask. * * The following events are available: * * - #RED_TRANSACT_UMOUNT * - #RED_TRANSACT_CREAT * - #RED_TRANSACT_UNLINK * - #RED_TRANSACT_MKDIR * - #RED_TRANSACT_RENAME * - #RED_TRANSACT_LINK * - #RED_TRANSACT_CLOSE * - #RED_TRANSACT_WRITE * - #RED_TRANSACT_FSYNC * - #RED_TRANSACT_TRUNCATE * - #RED_TRANSACT_VOLFULL * * The #RED_TRANSACT_MANUAL macro (by itself) may be used to disable all * automatic transaction events. The #RED_TRANSACT_MASK macro is a bitmask * of all transaction flags, excluding those representing excluded * functionality. * * Attempting to enable events for excluded functionality will result in an * error. * * @param pszVolume The path prefix of the volume whose transaction mask is * being changed. * @param ulEventMask A bitwise-OR'd mask of automatic transaction events to * be set as the current transaction mode. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #RED_EINVAL: Volume is not mounted; or @p pszVolume is `NULL`; or * @p ulEventMask contains invalid bits. * - #RED_ENOENT: @p pszVolume is not a valid volume path prefix. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_settransmask( const char * pszVolume, uint32_t ulEventMask ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { uint8_t bVolNum; ret = RedPathSplit( pszVolume, &bVolNum, NULL ); #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( bVolNum ); } #endif if( ret == 0 ) { ret = RedCoreTransMaskSet( ulEventMask ); } PosixLeave(); } return PosixReturn( ret ); } #endif /* if REDCONF_READ_ONLY == 0 */ /** @brief Read the transaction mask. * * If the volume is read-only, the returned event mask is always zero. * * @param pszVolume The path prefix of the volume whose transaction mask is * being retrieved. * @param pulEventMask Populated with a bitwise-OR'd mask of automatic * transaction events which represent the current * transaction mode for the volume. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #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. * * Errno values * - #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. * * Errno values * - #RED_EEXIST: Using #RED_O_CREAT and #RED_O_EXCL, and the indicated path * already exists. * - #RED_EINVAL: @p ulOpenMode is invalid; or @p pszPath is `NULL`; or the * volume containing the path is not mounted. * - #RED_EIO: A disk I/O error occurred. * - #RED_EISDIR: The path names a directory and @p ulOpenMode includes #RED_O_WRONLY or #RED_O_RDWR. * - #RED_EMFILE: There are no available file descriptors. * - #RED_ENAMETOOLONG: The length of a component of @p pszPath is longer than #REDCONF_NAME_MAX. * - #RED_ENFILE: Attempting to create a file but the file system has used all * available inode slots. * - #RED_ENOENT: #RED_O_CREAT is not set and the named file does not exist; or #RED_O_CREAT is set and the parent directory does not exist; or the * volume does not exist; or the @p pszPath argument, after removing the * volume prefix, points to an empty string. * - #RED_ENOSPC: The file does not exist and #RED_O_CREAT was specified, but * there is insufficient free space to expand the directory or to create the * new file. * - #RED_ENOTDIR: A component of the prefix in @p pszPath does not name a * directory. * - #RED_EROFS: The path resides on a read-only file system and a write * operation was requested. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_open( const char * pszPath, uint32_t ulOpenMode ) { int32_t iFildes = -1; /* Init'd to quiet warnings. */ REDSTATUS ret; #if REDCONF_READ_ONLY == 1 if( ulOpenMode != RED_O_RDONLY ) { ret = -RED_EROFS; } #else if( ( ulOpenMode != ( ulOpenMode & RED_O_MASK ) ) || ( ( ulOpenMode & ( RED_O_RDONLY | RED_O_WRONLY | RED_O_RDWR ) ) == 0U ) || ( ( ( ulOpenMode & RED_O_RDONLY ) != 0U ) && ( ( ulOpenMode & ( RED_O_WRONLY | RED_O_RDWR ) ) != 0U ) ) || ( ( ( ulOpenMode & RED_O_WRONLY ) != 0U ) && ( ( ulOpenMode & ( RED_O_RDONLY | RED_O_RDWR ) ) != 0U ) ) || ( ( ( ulOpenMode & RED_O_RDWR ) != 0U ) && ( ( ulOpenMode & ( RED_O_RDONLY | RED_O_WRONLY ) ) != 0U ) ) || ( ( ( ulOpenMode & ( RED_O_TRUNC | RED_O_CREAT | RED_O_EXCL ) ) != 0U ) && ( ( ulOpenMode & RED_O_RDONLY ) != 0U ) ) || ( ( ( ulOpenMode & RED_O_EXCL ) != 0U ) && ( ( ulOpenMode & RED_O_CREAT ) == 0U ) ) ) { ret = -RED_EINVAL; } #if REDCONF_API_POSIX_FTRUNCATE == 0 else if( ( ulOpenMode & RED_O_TRUNC ) != 0U ) { ret = -RED_EINVAL; } #endif #endif /* if REDCONF_READ_ONLY == 1 */ else { ret = PosixEnter(); } if( ret == 0 ) { ret = FildesOpen( pszPath, ulOpenMode, FTYPE_EITHER, &iFildes ); PosixLeave(); } if( ret != 0 ) { iFildes = PosixReturn( ret ); } return iFildes; } #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_UNLINK == 1 ) /** @brief Delete a file or directory. * * The given name is deleted and the link count of the corresponding inode is * decremented. If the link count falls to zero (no remaining hard links), * the inode will be deleted. * * Unlike POSIX unlink, deleting a file or directory with open handles (file * descriptors or directory streams) will fail with an #RED_EBUSY error. This * only applies when deleting an inode with a link count of one; if a file has * multiple names (hard links), all but the last name may be deleted even if * the file is open. * * If the path names a directory which is not empty, the unlink will fail. * * If the deletion frees data in the committed state, it will not return to * free space until after a transaction point. * * Unlike POSIX unlink, this function can fail when the disk is full. To fix * this, transact and try again: Reliance Edge guarantees that it is possible * to delete at least one file or directory after a transaction point. If disk * full automatic transactions are enabled, this will happen automatically. * * @param pszPath The path of the file or directory to delete. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #RED_EBUSY: @p pszPath points to an inode with open handles and a link * count of one. * - #RED_EINVAL: @p pszPath is `NULL`; or the volume containing the path is * not mounted. * - #RED_EIO: A disk I/O error occurred. * - #RED_ENAMETOOLONG: The length of a component of @p pszPath is longer than #REDCONF_NAME_MAX. * - #RED_ENOENT: The path does not name an existing file; or the @p pszPath * argument, after removing the volume prefix, points to an empty string. * - #RED_ENOTDIR: A component of the path prefix is not a directory. * - #RED_ENOTEMPTY: The path names a directory which is not empty. * - #RED_ENOSPC: The file system does not have enough space to modify the * parent directory to perform the deletion. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_unlink( const char * pszPath ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { ret = UnlinkSub( pszPath, FTYPE_EITHER ); PosixLeave(); } return PosixReturn( ret ); } #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_UNLINK == 1 ) */ #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_MKDIR == 1 ) /** @brief Create a new directory. * * Unlike POSIX mkdir, this function has no second argument for the * permissions (which Reliance Edge does not use). * * @param pszPath The name and location of the directory to create. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #RED_EEXIST: @p pszPath points to an existing file or directory. * - #RED_EINVAL: @p pszPath is `NULL`; or the volume containing the path is * not mounted. * - #RED_EIO: A disk I/O error occurred. * - #RED_ENAMETOOLONG: The length of a component of @p pszPath is longer than #REDCONF_NAME_MAX. * - #RED_ENOENT: A component of the path prefix does not name an existing * directory; or the @p pszPath argument, after removing the volume prefix, * points to an empty string. * - #RED_ENOSPC: The file system does not have enough space for the new * directory or to extend the parent directory of the new directory. * - #RED_ENOTDIR: A component of the path prefix is not a directory. * - #RED_EROFS: The parent directory resides on a read-only file system. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_mkdir( const char * pszPath ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { const char * pszLocalPath; uint8_t bVolNum; ret = RedPathSplit( pszPath, &bVolNum, &pszLocalPath ); #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( bVolNum ); } #endif if( ret == 0 ) { const char * pszName; uint32_t ulPInode; ret = RedPathToName( pszLocalPath, &ulPInode, &pszName ); if( ret == 0 ) { uint32_t ulInode; ret = RedCoreCreate( ulPInode, pszName, true, &ulInode ); } } PosixLeave(); } return PosixReturn( ret ); } #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_MKDIR == 1 ) */ #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_RMDIR == 1 ) /** @brief Delete a directory. * * The given directory name is deleted and the corresponding directory inode * will be deleted. * * Unlike POSIX rmdir, deleting a directory with open handles (file * descriptors or directory streams) will fail with an #RED_EBUSY error. * * If the path names a directory which is not empty, the deletion will fail. * If the path names the root directory of a file system volume, the deletion * will fail. * * If the path names a regular file, the deletion will fail. This provides * type checking and may be useful in cases where an application knows the * path to be deleted should name a directory. * * If the deletion frees data in the committed state, it will not return to * free space until after a transaction point. * * Unlike POSIX rmdir, this function can fail when the disk is full. To fix * this, transact and try again: Reliance Edge guarantees that it is possible * to delete at least one file or directory after a transaction point. If disk * full automatic transactions are enabled, this will happen automatically. * * @param pszPath The path of the directory to delete. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #RED_EBUSY: @p pszPath points to a directory with open handles. * - #RED_EINVAL: @p pszPath is `NULL`; or the volume containing the path is * not mounted. * - #RED_EIO: A disk I/O error occurred. * - #RED_ENAMETOOLONG: The length of a component of @p pszPath is longer than #REDCONF_NAME_MAX. * - #RED_ENOENT: The path does not name an existing directory; or the * @p pszPath argument, after removing the volume prefix, points to an empty * string. * - #RED_ENOTDIR: A component of the path is not a directory. * - #RED_ENOTEMPTY: The path names a directory which is not empty. * - #RED_ENOSPC: The file system does not have enough space to modify the * parent directory to perform the deletion. * - #RED_EROFS: The directory to be removed resides on a read-only file * system. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_rmdir( const char * pszPath ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { ret = UnlinkSub( pszPath, FTYPE_DIR ); PosixLeave(); } return PosixReturn( ret ); } #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_RMDIR == 1 ) */ #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_RENAME == 1 ) /** @brief Rename a file or directory. * * Both paths must reside on the same file system volume. Attempting to use * this API to move a file to a different volume will result in an error. * * If @p pszNewPath names an existing file or directory, the behavior depends * on the configuration. If #REDCONF_RENAME_ATOMIC is false, and if the * destination name exists, this function always fails and sets #red_errno to #RED_EEXIST. This behavior is contrary to POSIX. * * If #REDCONF_RENAME_ATOMIC is true, and if the new name exists, then in one * atomic operation, @p pszNewPath is unlinked and @p pszOldPath is renamed to * @p pszNewPath. Both @p pszNewPath and @p pszOldPath must be of the same * type (both files or both directories). As with red_unlink(), if * @p pszNewPath is a directory, it must be empty. The major exception to this * behavior is that if both @p pszOldPath and @p pszNewPath are links to the * same inode, then the rename does nothing and both names continue to exist. * Unlike POSIX rename, if @p pszNewPath points to an inode with a link count * of one and open handles (file descriptors or directory streams), the * rename will fail with #RED_EBUSY. * * If the rename deletes the old destination, it may free data in the * committed state, which will not return to free space until after a * transaction point. Similarly, if the deleted inode was part of the * committed state, the inode slot will not be available until after a * transaction point. * * @param pszOldPath The path of the file or directory to rename. * @param pszNewPath The new name and location after the rename. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #RED_EBUSY: #REDCONF_RENAME_ATOMIC is true and @p pszNewPath points to an * inode with open handles and a link count of one. * - #RED_EEXIST: #REDCONF_RENAME_ATOMIC is false and @p pszNewPath exists. * - #RED_EINVAL: @p pszOldPath is `NULL`; or @p pszNewPath is `NULL`; or the * volume containing the path is not mounted. * - #RED_EIO: A disk I/O error occurred. * - #RED_EISDIR: The @p pszNewPath argument names a directory and the * @p pszOldPath argument names a non-directory. * - #RED_ENAMETOOLONG: The length of a component of either @p pszOldPath or * @p pszNewPath is longer than #REDCONF_NAME_MAX. * - #RED_ENOENT: The link named by @p pszOldPath does not name an existing * entry; or either @p pszOldPath or @p pszNewPath, after removing the volume * prefix, point to an empty string. * - #RED_ENOTDIR: A component of either path prefix is not a directory; or * @p pszOldPath names a directory and @p pszNewPath names a file. * - #RED_ENOTEMPTY: The path named by @p pszNewPath is a directory which is * not empty. * - #RED_ENOSPC: The file system does not have enough space to extend the * directory that would contain @p pszNewPath. * - #RED_EROFS: The directory to be removed resides on a read-only file * system. * - #RED_EUSERS: Cannot become a file system user: too many users. * - #RED_EXDEV: @p pszOldPath and @p pszNewPath are on different file system * volumes. */ int32_t red_rename( const char * pszOldPath, const char * pszNewPath ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { const char * pszOldLocalPath; uint8_t bOldVolNum; ret = RedPathSplit( pszOldPath, &bOldVolNum, &pszOldLocalPath ); if( ret == 0 ) { const char * pszNewLocalPath; uint8_t bNewVolNum; ret = RedPathSplit( pszNewPath, &bNewVolNum, &pszNewLocalPath ); if( ( ret == 0 ) && ( bOldVolNum != bNewVolNum ) ) { ret = -RED_EXDEV; } #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( bOldVolNum ); } #endif if( ret == 0 ) { const char * pszOldName; uint32_t ulOldPInode; ret = RedPathToName( pszOldLocalPath, &ulOldPInode, &pszOldName ); if( ret == 0 ) { const char * pszNewName; uint32_t ulNewPInode; ret = RedPathToName( pszNewLocalPath, &ulNewPInode, &pszNewName ); #if REDCONF_RENAME_ATOMIC == 1 if( ret == 0 ) { uint32_t ulDestInode; ret = RedCoreLookup( ulNewPInode, pszNewName, &ulDestInode ); if( ret == 0 ) { ret = InodeUnlinkCheck( ulDestInode ); } else if( ret == -RED_ENOENT ) { ret = 0; } else { /* Unexpected error, nothing to do. */ } } #endif /* if REDCONF_RENAME_ATOMIC == 1 */ if( ret == 0 ) { ret = RedCoreRename( ulOldPInode, pszOldName, ulNewPInode, pszNewName ); } } } } PosixLeave(); } return PosixReturn( ret ); } #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_RENAME == 1 ) */ #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_LINK == 1 ) /** @brief Create a hard link. * * This creates an additional name (link) for the file named by @p pszPath. * The new name refers to the same file with the same contents. If a name is * deleted, but the underlying file has other names, the file continues to * exist. The link count (accessible via red_fstat()) indicates the number of * names that a file has. All of a file's names are on equal footing: there * is nothing special about the original name. * * If @p pszPath names a directory, the operation will fail. * * @param pszPath The path indicating the inode for the new link. * @param pszHardLink The name and location for the new link. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #RED_EEXIST: @p pszHardLink resolves to an existing file. * - #RED_EINVAL: @p pszPath or @p pszHardLink is `NULL`; or the volume * containing the paths is not mounted. * - #RED_EIO: A disk I/O error occurred. * - #RED_EMLINK: Creating the link would exceed the maximum link count of the * inode named by @p pszPath. * - #RED_ENAMETOOLONG: The length of a component of either @p pszPath or * @p pszHardLink is longer than #REDCONF_NAME_MAX. * - #RED_ENOENT: A component of either path prefix does not exist; or the file * named by @p pszPath does not exist; or either @p pszPath or * @p pszHardLink, after removing the volume prefix, point to an empty * string. * - #RED_ENOSPC: There is insufficient free space to expand the directory that * would contain the link. * - #RED_ENOTDIR: A component of either path prefix is not a directory. * - #RED_EPERM: The @p pszPath argument names a directory. * - #RED_EROFS: The requested link requires writing in a directory on a * read-only file system. * - #RED_EUSERS: Cannot become a file system user: too many users. * - #RED_EXDEV: @p pszPath and @p pszHardLink are on different file system * volumes. */ int32_t red_link( const char * pszPath, const char * pszHardLink ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { const char * pszLocalPath; uint8_t bVolNum; ret = RedPathSplit( pszPath, &bVolNum, &pszLocalPath ); if( ret == 0 ) { const char * pszLinkLocalPath; uint8_t bLinkVolNum; ret = RedPathSplit( pszHardLink, &bLinkVolNum, &pszLinkLocalPath ); if( ( ret == 0 ) && ( bVolNum != bLinkVolNum ) ) { ret = -RED_EXDEV; } #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( bVolNum ); } #endif if( ret == 0 ) { uint32_t ulInode; ret = RedPathLookup( pszLocalPath, &ulInode ); if( ret == 0 ) { const char * pszLinkName; uint32_t ulLinkPInode; ret = RedPathToName( pszLinkLocalPath, &ulLinkPInode, &pszLinkName ); if( ret == 0 ) { ret = RedCoreLink( ulLinkPInode, pszLinkName, ulInode ); } } } } PosixLeave(); } return PosixReturn( ret ); } #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_LINK == 1 ) */ /** @brief Close a file descriptor. * * @param iFildes The file descriptor to close. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #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. * * Errno values * - #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. * * Errno values * - #RED_EBADF: The @p iFildes argument is not a valid file descriptor open * for writing. This includes the case where the file descriptor is for a * directory. * - #RED_EFBIG: No data can be written to the current file offset since the * resulting file size would exceed the maximum file size. * - #RED_EINVAL: @p pBuffer is `NULL`; or @p ulLength exceeds INT32_MAX and * cannot be returned properly. * - #RED_EIO: A disk I/O error occurred. * - #RED_ENOSPC: No data can be written because there is insufficient free * space. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_write( int32_t iFildes, const void * pBuffer, uint32_t ulLength ) { uint32_t ulLenWrote = 0U; REDSTATUS ret; int32_t iReturn; if( ulLength > ( uint32_t ) INT32_MAX ) { ret = -RED_EINVAL; } else { ret = PosixEnter(); } if( ret == 0 ) { REDHANDLE * pHandle; ret = FildesToHandle( iFildes, FTYPE_FILE, &pHandle ); if( ret == -RED_EISDIR ) { /* POSIX says that if a file descriptor is not writable, the * errno should be -RED_EBADF. Directory file descriptors are * never writable, and unlike for read(), the spec does not * list -RED_EISDIR as an allowed errno. Therefore -RED_EBADF * takes precedence. */ ret = -RED_EBADF; } if( ( ret == 0 ) && ( ( pHandle->bFlags & HFLAG_WRITEABLE ) == 0U ) ) { ret = -RED_EBADF; } #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( pHandle->bVolNum ); } #endif if( ( ret == 0 ) && ( ( pHandle->bFlags & HFLAG_APPENDING ) != 0U ) ) { REDSTAT s; ret = RedCoreStat( pHandle->ulInode, &s ); if( ret == 0 ) { pHandle->ullOffset = s.st_size; } } if( ret == 0 ) { ulLenWrote = ulLength; ret = RedCoreFileWrite( pHandle->ulInode, pHandle->ullOffset, &ulLenWrote, pBuffer ); } if( ret == 0 ) { REDASSERT( ulLenWrote <= ulLength ); pHandle->ullOffset += ulLenWrote; } PosixLeave(); } if( ret == 0 ) { iReturn = ( int32_t ) ulLenWrote; } else { iReturn = PosixReturn( ret ); } return iReturn; } #endif /* if REDCONF_READ_ONLY == 0 */ #if REDCONF_READ_ONLY == 0 /** @brief Synchronizes changes to a file. * * Commits all changes associated with a file or directory (including file * data, directory contents, and metadata) to permanent storage. This * function will not return until the operation is complete. * * In the current implementation, this function has global effect. All dirty * buffers are flushed and a transaction point is committed. Fsyncing one * file effectively fsyncs all files. * * If fsync automatic transactions have been disabled, this function does * nothing and returns success. In the current implementation, this is the * only real difference between this function and red_transact(): this * function can be configured to do nothing, whereas red_transact() is * unconditional. * * Applications written for portability should avoid assuming red_fsync() * effects all files, and use red_fsync() on each file that needs to be * synchronized. * * Passing read-only file descriptors to this function is permitted. * * @param iFildes The file descriptor to synchronize. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #RED_EBADF: The @p iFildes argument is not a valid file descriptor. * - #RED_EIO: A disk I/O error occurred. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_fsync( int32_t iFildes ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { REDHANDLE * pHandle; ret = FildesToHandle( iFildes, FTYPE_EITHER, &pHandle ); #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( pHandle->bVolNum ); } #endif /* No core event for fsync, so this transaction flag needs to be * implemented here. */ if( ret == 0 ) { uint32_t ulTransMask; ret = RedCoreTransMaskGet( &ulTransMask ); if( ( ret == 0 ) && ( ( ulTransMask & RED_TRANSACT_FSYNC ) != 0U ) ) { ret = RedCoreVolTransact(); } } PosixLeave(); } return PosixReturn( ret ); } #endif /* if REDCONF_READ_ONLY == 0 */ /** @brief Move the read/write file offset. * * The file offset of the @p iFildes file descriptor is set to @p llOffset, * relative to some starting position. The available positions are: * * - ::RED_SEEK_SET Seek from the start of the file. In other words, * @p llOffset becomes the new file offset. * - ::RED_SEEK_CUR Seek from the current file offset. In other words, * @p llOffset is added to the current file offset. * - ::RED_SEEK_END Seek from the end-of-file. In other words, the new file * offset is the file size plus @p llOffset. * * Since @p llOffset is signed (can be negative), it is possible to seek * backward with ::RED_SEEK_CUR or ::RED_SEEK_END. * * It is permitted to seek beyond the end-of-file; this does not increase the * file size (a subsequent red_write() call would). * * Unlike POSIX lseek, this function cannot be used with directory file * descriptors. * * @param iFildes The file descriptor whose offset is to be updated. * @param llOffset The new file offset, relative to @p whence. * @param whence The location from which @p llOffset should be applied. * * @return On success, returns the new file position, measured in bytes from * the beginning of the file. On error, -1 is returned and #red_errno * is set appropriately. * * Errno values * - #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. * * Errno values * - #RED_EBADF: The @p iFildes argument is not a valid file descriptor open * for writing. This includes the case where the file descriptor is for a * directory. * - #RED_EFBIG: @p ullSize exceeds the maximum file size. * - #RED_EIO: A disk I/O error occurred. * - #RED_ENOSPC: Insufficient free space to perform the truncate. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_ftruncate( int32_t iFildes, uint64_t ullSize ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { REDHANDLE * pHandle; ret = FildesToHandle( iFildes, FTYPE_FILE, &pHandle ); if( ret == -RED_EISDIR ) { /* Similar to red_write() (see comment there), the RED_EBADF error * for a non-writable file descriptor takes precedence. */ ret = -RED_EBADF; } if( ( ret == 0 ) && ( ( pHandle->bFlags & HFLAG_WRITEABLE ) == 0U ) ) { ret = -RED_EBADF; } #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( pHandle->bVolNum ); } #endif if( ret == 0 ) { ret = RedCoreFileTruncate( pHandle->ulInode, ullSize ); } PosixLeave(); } return PosixReturn( ret ); } #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_FTRUNCATE == 1 ) */ /** @brief Get the status of a file or directory. * * See the ::REDSTAT type for the details of the information returned. * * @param iFildes An open file descriptor for the file whose information is * to be retrieved. * @param pStat Pointer to a ::REDSTAT buffer to populate. * * @return On success, zero is returned. On error, -1 is returned and #red_errno is set appropriately. * * Errno values * - #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. * * Errno values * - #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. * * Errno values * - #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. * * Errno values * - #RED_EBADF: @p pDirStream is not an open directory stream. * - #RED_EUSERS: Cannot become a file system user: too many users. */ int32_t red_closedir( REDDIR * pDirStream ) { REDSTATUS ret; ret = PosixEnter(); if( ret == 0 ) { if( DirStreamIsValid( pDirStream ) ) { /* Mark this handle as unused. */ pDirStream->ulInode = INODE_INVALID; } else { ret = -RED_EBADF; } PosixLeave(); } return PosixReturn( ret ); } #endif /* REDCONF_API_POSIX_READDIR */ /** @brief Pointer to where the last file system error (errno) is stored. * * This function is intended to be used via the #red_errno macro, or a similar * user-defined macro, that can be used both as an lvalue (writable) and an * rvalue (readable). * * Under normal circumstances, the errno for each task is stored in a * different location. Applications do not need to worry about one task * obliterating an error value that another task needed to read. This task * errno for is initially zero. When one of the POSIX-like APIs returns an * indication of error, the location for the calling task will be populated * with the error value. * * In some circumstances, this function will return a pointer to a global * errno location which is shared by multiple tasks. If the calling task is * not registered as a file system user and all of the task slots are full, * there can be no task-specific errno, so the global pointer is returned. * Likewise, if the file system driver is uninitialized, there are no * registered file system users and this function always returns the pointer * to the global errno. Under these circumstances, multiple tasks * manipulating errno could be problematic. * * This function never returns `NULL` under any circumstances. The #red_errno * macro unconditionally dereferences the return value from this function, so * returning `NULL` could result in a fault. * * @return Pointer to where the errno value is stored for this task. */ REDSTATUS * red_errnoptr( void ) { /* The global errno value, used in single-task configurations and when the * caller is not (and cannot become) a file system user (which includes * when the driver is uninitialized). */ static REDSTATUS iGlobalErrno = 0; #if REDCONF_TASK_COUNT == 1U return &iGlobalErrno; #else REDSTATUS * piErrno; if( gfPosixInited ) { uint32_t ulTaskId = RedOsTaskId(); uint32_t ulIdx; REDASSERT( ulTaskId != 0U ); /* If this task has used the file system before, it will already have * a task slot, which includes the task-specific errno. */ RedOsMutexAcquire(); for( ulIdx = 0U; ulIdx < REDCONF_TASK_COUNT; ulIdx++ ) { if( gaTask[ ulIdx ].ulTaskId == ulTaskId ) { break; } } RedOsMutexRelease(); if( ulIdx == REDCONF_TASK_COUNT ) { REDSTATUS ret; /* This task is not a file system user, so try to register it as * one. This FS mutex must be held in order to register. */ RedOsMutexAcquire(); ret = TaskRegister( &ulIdx ); RedOsMutexRelease(); if( ret == 0 ) { REDASSERT( gaTask[ ulIdx ].ulTaskId == RedOsTaskId() ); REDASSERT( gaTask[ ulIdx ].iErrno == 0 ); piErrno = &gaTask[ ulIdx ].iErrno; } else { /* Unable to register; use the global errno. */ piErrno = &iGlobalErrno; } } else { piErrno = &gaTask[ ulIdx ].iErrno; } } else { /* There are no registered file system tasks when the driver is * uninitialized, so use the global errno. */ piErrno = &iGlobalErrno; } /* This function is not allowed to return NULL. */ REDASSERT( piErrno != NULL ); return piErrno; #endif /* if REDCONF_TASK_COUNT == 1U */ } /** @} */ /*------------------------------------------------------------------- * Helper Functions * -------------------------------------------------------------------*/ #if ( REDCONF_READ_ONLY == 0 ) && ( ( REDCONF_API_POSIX_UNLINK == 1 ) || ( REDCONF_API_POSIX_RMDIR == 1 ) ) /** @brief Remove a link to a file or directory. * * If the link count becomes zero, the file or directory is deleted. * * @param pszPath Path of the link to remove. * @param type The expected type of the path: file, directory, or either. * An error is returned if the expected type is file or * directory and does not match the path. * * @return A negated ::REDSTATUS code indicating the operation result. * * @retval -RED_EBUSY @p pszPath points to an inode with open handles * and a link count of one. * @retval -RED_EINVAL @p pszPath is `NULL`; or the volume containing * the path is not mounted. * @retval -RED_EIO A disk I/O error occurred. * @retval -RED_EISDIR @p type is ::FTYPE_FILE and the path names a * directory. * @retval -RED_ENAMETOOLONG @p pszName is too long. * @retval -RED_ENOENT The path does not name an existing file; or * @p pszPath, after removing the volume prefix, * points to an empty string. * @retval -RED_ENOTDIR @p type is ::FTYPE_DIR and the path does not * name a directory. * @retval -RED_ENOTEMPTY @p pszPath is a directory which is not empty. * @retval -RED_ENOSPC The file system does not have enough space to * modify the parent directory to perform the * deletion. */ static REDSTATUS UnlinkSub( const char * pszPath, FTYPE type ) { uint8_t bVolNum; const char * pszLocalPath; REDSTATUS ret; ret = RedPathSplit( pszPath, &bVolNum, &pszLocalPath ); #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( bVolNum ); } #endif if( ret == 0 ) { const char * pszName; uint32_t ulPInode; ret = RedPathToName( pszLocalPath, &ulPInode, &pszName ); if( ret == 0 ) { uint32_t ulInode; ret = RedCoreLookup( ulPInode, pszName, &ulInode ); /* ModeTypeCheck() always passes when the type is FTYPE_EITHER, so * skip stat'ing the inode in that case. */ if( ( ret == 0 ) && ( type != FTYPE_EITHER ) ) { REDSTAT InodeStat; ret = RedCoreStat( ulInode, &InodeStat ); if( ret == 0 ) { ret = ModeTypeCheck( InodeStat.st_mode, type ); } } if( ret == 0 ) { ret = InodeUnlinkCheck( ulInode ); } if( ret == 0 ) { ret = RedCoreUnlink( ulPInode, pszName ); } } } return ret; } #endif /* (REDCONF_API_POSIX_UNLINK == 1) || (REDCONF_API_POSIX_RMDIR == 1) */ /** @brief Get a file descriptor for a path. * * @param pszPath Path to a file to open. * @param ulOpenMode The RED_O_* flags the descriptor is opened with. * @param type Indicates the expected descriptor type: file, directory, * or either. * @param piFildes On successful return, populated with the file * descriptor. * * @return A negated ::REDSTATUS code indicating the operation result. * * @retval 0 Operation was successful. * @retval -RED_EINVAL @p piFildes is `NULL`; or @p pszPath is `NULL`; * or the volume is not mounted. * @retval -RED_EMFILE There are no available handles. * @retval -RED_EEXIST Using #RED_O_CREAT and #RED_O_EXCL, and the * indicated path already exists. * @retval -RED_EISDIR The path names a directory and @p ulOpenMode * includes #RED_O_WRONLY or #RED_O_RDWR. * @retval -RED_ENOENT #RED_O_CREAT is not set and the named file does * not exist; or #RED_O_CREAT is set and the parent * directory does not exist; or the volume does not * exist; or the @p pszPath argument, after * removing the volume prefix, points to an empty * string. * @retval -RED_EIO A disk I/O error occurred. * @retval -RED_ENAMETOOLONG The length of a component of @p pszPath is * longer than #REDCONF_NAME_MAX. * @retval -RED_ENFILE Attempting to create a file but the file system * has used all available inode slots. * @retval -RED_ENOSPC The file does not exist and #RED_O_CREAT was * specified, but there is insufficient free space * to expand the directory or to create the new * file. * @retval -RED_ENOTDIR A component of the prefix in @p pszPath does not * name a directory. * @retval -RED_EROFS The path resides on a read-only file system and * a write operation was requested. */ static REDSTATUS FildesOpen( const char * pszPath, uint32_t ulOpenMode, FTYPE type, int32_t * piFildes ) { uint8_t bVolNum; const char * pszLocalPath; REDSTATUS ret; ret = RedPathSplit( pszPath, &bVolNum, &pszLocalPath ); if( ret == 0 ) { if( piFildes == NULL ) { ret = -RED_EINVAL; } #if REDCONF_READ_ONLY == 0 else if( gaRedVolume[ bVolNum ].fReadOnly && ( ulOpenMode != RED_O_RDONLY ) ) { ret = -RED_EROFS; } #endif else { uint16_t uHandleIdx; REDHANDLE * pHandle = NULL; /* Search for an unused handle. */ for( uHandleIdx = 0U; uHandleIdx < REDCONF_HANDLE_COUNT; uHandleIdx++ ) { if( gaHandle[ uHandleIdx ].ulInode == INODE_INVALID ) { pHandle = &gaHandle[ uHandleIdx ]; break; } } /* Error if all the handles are in use. */ if( pHandle == NULL ) { ret = -RED_EMFILE; } else { bool fCreated = false; uint16_t uMode = 0U; uint32_t ulInode = 0U; /* Init'd to quiet warnings. */ #if REDCONF_VOLUME_COUNT > 1U ret = RedCoreVolSetCurrent( bVolNum ); if( ret == 0 ) #endif { #if REDCONF_READ_ONLY == 0 if( ( ulOpenMode & RED_O_CREAT ) != 0U ) { uint32_t ulPInode; const char * pszName; ret = RedPathToName( pszLocalPath, &ulPInode, &pszName ); if( ret == 0 ) { ret = RedCoreCreate( ulPInode, pszName, false, &ulInode ); if( ret == 0 ) { fCreated = true; } else if( ( ret == -RED_EEXIST ) && ( ( ulOpenMode & RED_O_EXCL ) == 0U ) ) { /* If the path already exists and that's OK, * lookup its inode number. */ ret = RedCoreLookup( ulPInode, pszName, &ulInode ); } else { /* No action, just propagate the error. */ } } } else #endif /* if REDCONF_READ_ONLY == 0 */ { ret = RedPathLookup( pszLocalPath, &ulInode ); } } /* If we created the inode, none of the below stuff is * necessary. This is important from an error handling * perspective -- we do not need code to delete the created * inode on error. */ if( !fCreated ) { if( ret == 0 ) { REDSTAT s; ret = RedCoreStat( ulInode, &s ); if( ret == 0 ) { uMode = s.st_mode; } } /* Error if the inode is not of the expected type. */ if( ret == 0 ) { ret = ModeTypeCheck( uMode, type ); } /* Directories must always be opened with O_RDONLY. */ if( ( ret == 0 ) && RED_S_ISDIR( uMode ) && ( ( ulOpenMode & RED_O_RDONLY ) == 0U ) ) { ret = -RED_EISDIR; } #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_FTRUNCATE == 1 ) if( ( ret == 0 ) && ( ( ulOpenMode & RED_O_TRUNC ) != 0U ) ) { ret = RedCoreFileTruncate( ulInode, UINT64_SUFFIX( 0 ) ); } #endif } if( ret == 0 ) { int32_t iFildes; RedMemSet( pHandle, 0U, sizeof( *pHandle ) ); /* Populate this handle, marking it as in use. */ pHandle->ulInode = ulInode; pHandle->bVolNum = bVolNum; if( RED_S_ISDIR( uMode ) ) { pHandle->bFlags |= HFLAG_DIRECTORY; } if( ( ( ulOpenMode & RED_O_RDONLY ) != 0U ) || ( ( ulOpenMode & RED_O_RDWR ) != 0U ) ) { pHandle->bFlags |= HFLAG_READABLE; } #if REDCONF_READ_ONLY == 0 if( ( ( ulOpenMode & RED_O_WRONLY ) != 0U ) || ( ( ulOpenMode & RED_O_RDWR ) != 0U ) ) { pHandle->bFlags |= HFLAG_WRITEABLE; } if( ( ulOpenMode & RED_O_APPEND ) != 0U ) { pHandle->bFlags |= HFLAG_APPENDING; } #endif iFildes = FildesPack( uHandleIdx, bVolNum ); if( iFildes == -1 ) { /* It should be impossible to get here, unless there * is memory corruption. */ REDERROR(); ret = -RED_EFUBAR; } else { *piFildes = iFildes; } } } } } return ret; } /** @brief Close a file descriptor. * * @param iFildes The file descriptor to close. * * @return A negated ::REDSTATUS code indicating the operation result. * * @retval 0 Operation was successful. * @retval -RED_EBADF @p iFildes is not a valid file descriptor. * @retval -RED_EIO A disk I/O error occurred. */ static REDSTATUS FildesClose( int32_t iFildes ) { REDHANDLE * pHandle; REDSTATUS ret; ret = FildesToHandle( iFildes, FTYPE_EITHER, &pHandle ); #if REDCONF_READ_ONLY == 0 #if REDCONF_VOLUME_COUNT > 1U if( ret == 0 ) { ret = RedCoreVolSetCurrent( pHandle->bVolNum ); } #endif /* No core event for close, so this transaction flag needs to be * implemented here. */ if( ret == 0 ) { uint32_t ulTransMask; ret = RedCoreTransMaskGet( &ulTransMask ); if( ( ret == 0 ) && ( ( ulTransMask & RED_TRANSACT_CLOSE ) != 0U ) ) { ret = RedCoreVolTransact(); } } #endif /* if REDCONF_READ_ONLY == 0 */ if( ret == 0 ) { /* Mark this handle as unused. */ pHandle->ulInode = INODE_INVALID; } return ret; } /** @brief Convert a file descriptor into a handle pointer. * * Also validates the file descriptor. * * @param iFildes The file descriptor for which to get a handle. * @param expectedType The expected type of the file descriptor: ::FTYPE_DIR, * ::FTYPE_FILE, or ::FTYPE_EITHER. * @param ppHandle On successful return, populated with a pointer to the * handle associated with @p iFildes. * * @return A negated ::REDSTATUS code indicating the operation result. * * @retval 0 Operation was successful. * @retval -RED_EBADF @p iFildes is not a valid file descriptor. * @retval -RED_EINVAL @p ppHandle is `NULL`. * @retval -RED_EISDIR Expected a file, but the file descriptor is for a * directory. * @retval -RED_ENOTDIR Expected a directory, but the file descriptor is for * a file. */ static REDSTATUS FildesToHandle( int32_t iFildes, FTYPE expectedType, REDHANDLE ** ppHandle ) { REDSTATUS ret; if( ppHandle == NULL ) { REDERROR(); ret = -RED_EINVAL; } else if( iFildes < FD_MIN ) { ret = -RED_EBADF; } else { uint16_t uHandleIdx; uint8_t bVolNum; uint16_t uGeneration; FildesUnpack( iFildes, &uHandleIdx, &bVolNum, &uGeneration ); if( ( uHandleIdx >= REDCONF_HANDLE_COUNT ) || ( bVolNum >= REDCONF_VOLUME_COUNT ) || ( gaHandle[ uHandleIdx ].ulInode == INODE_INVALID ) || ( gaHandle[ uHandleIdx ].bVolNum != bVolNum ) || ( gauGeneration[ bVolNum ] != uGeneration ) ) { ret = -RED_EBADF; } else if( ( expectedType == FTYPE_FILE ) && ( ( gaHandle[ uHandleIdx ].bFlags & HFLAG_DIRECTORY ) != 0U ) ) { ret = -RED_EISDIR; } else if( ( expectedType == FTYPE_DIR ) && ( ( gaHandle[ uHandleIdx ].bFlags & HFLAG_DIRECTORY ) == 0U ) ) { ret = -RED_ENOTDIR; } else { *ppHandle = &gaHandle[ uHandleIdx ]; ret = 0; } } return ret; } /** @brief Pack a file descriptor. * * @param uHandleIdx The index of the file handle that will be associated * with this file descriptor. * @param bVolNum The volume which contains the file or directory this * file descriptor was opened against. * * @return The packed file descriptor. */ static int32_t FildesPack( uint16_t uHandleIdx, uint8_t bVolNum ) { int32_t iFildes; if( ( uHandleIdx >= REDCONF_HANDLE_COUNT ) || ( bVolNum >= REDCONF_VOLUME_COUNT ) ) { REDERROR(); iFildes = -1; } else { uint32_t ulFdBits; REDASSERT( gauGeneration[ bVolNum ] <= FD_GEN_MAX ); REDASSERT( gauGeneration[ bVolNum ] != 0U ); ulFdBits = gauGeneration[ bVolNum ]; ulFdBits <<= FD_VOL_BITS; ulFdBits |= bVolNum; ulFdBits <<= FD_IDX_BITS; ulFdBits |= uHandleIdx; iFildes = ( int32_t ) ulFdBits; if( iFildes < FD_MIN ) { REDERROR(); iFildes = -1; } } return iFildes; } /** @brief Unpack a file descriptor. * * @param iFildes The file descriptor to unpack. * @param puHandleIdx If non-NULL, populated with the handle index extracted * from the file descriptor. * @param pbVolNum If non-NULL, populated with the volume number extracted * from the file descriptor. * @param puGeneration If non-NULL, populated with the generation number * extracted from the file descriptor. */ static void FildesUnpack( int32_t iFildes, uint16_t * puHandleIdx, uint8_t * pbVolNum, uint16_t * puGeneration ) { uint32_t ulFdBits = ( uint32_t ) iFildes; REDASSERT( iFildes >= FD_MIN ); if( puHandleIdx != NULL ) { *puHandleIdx = ( uint16_t ) ( ulFdBits & FD_IDX_MAX ); } ulFdBits >>= FD_IDX_BITS; if( pbVolNum != NULL ) { *pbVolNum = ( uint8_t ) ( ulFdBits & FD_VOL_MAX ); } ulFdBits >>= FD_VOL_BITS; if( puGeneration != NULL ) { *puGeneration = ( uint16_t ) ( ulFdBits & FD_GEN_MAX ); } } #if REDCONF_API_POSIX_READDIR == 1 /** @brief Validate a directory stream object. * * @param pDirStream The directory stream to validate. * * @return Whether the directory stream is valid. * * @retval true The directory stream object appears valid. * @retval false The directory stream object is invalid. */ static bool DirStreamIsValid( const REDDIR * pDirStream ) { bool fRet = true; if( pDirStream == NULL ) { fRet = false; } else { uint16_t uHandleIdx; /* pDirStream should be a pointer to one of the handles. * * A good compiler will optimize this loop into a bounds check and an * alignment check. */ for( uHandleIdx = 0U; uHandleIdx < REDCONF_HANDLE_COUNT; uHandleIdx++ ) { if( pDirStream == &gaHandle[ uHandleIdx ] ) { break; } } if( uHandleIdx < REDCONF_HANDLE_COUNT ) { /* The handle must be in use, have a valid volume number, and be a * directory handle. */ if( ( pDirStream->ulInode == INODE_INVALID ) || ( pDirStream->bVolNum >= REDCONF_VOLUME_COUNT ) || ( ( pDirStream->bFlags & HFLAG_DIRECTORY ) == 0U ) ) { fRet = false; } } else { /* pDirStream is a non-null pointer, but it is not a pointer to one * of our handles. */ fRet = false; } } return fRet; } #endif /* if REDCONF_API_POSIX_READDIR == 1 */ /** @brief Enter the file system driver. * * @return A negated ::REDSTATUS code indicating the operation result. * * @retval 0 Operation was successful. * @retval -RED_EINVAL The file system driver is uninitialized. * @retval -RED_EUSERS Cannot become a file system user: too many users. */ static REDSTATUS PosixEnter( void ) { REDSTATUS ret; if( gfPosixInited ) { #if REDCONF_TASK_COUNT > 1U RedOsMutexAcquire(); ret = TaskRegister( NULL ); if( ret != 0 ) { RedOsMutexRelease(); } #else ret = 0; #endif } else { ret = -RED_EINVAL; } return ret; } /** @brief Leave the file system driver. */ static void PosixLeave( void ) { /* If the driver was uninitialized, PosixEnter() should have failed and we * should not be calling PosixLeave(). */ REDASSERT( gfPosixInited ); #if REDCONF_TASK_COUNT > 1U RedOsMutexRelease(); #endif } /** @brief Check that a mode is consistent with the given expected type. * * @param uMode An inode mode, indicating whether the inode is a file * or a directory. * @param expectedType The expected type: ::FTYPE_FILE, ::FTYPE_DIR, or * ::FTYPE_EITHER. * * @return A negated ::REDSTATUS code indicating the operation result. * * @retval 0 Operation was successful. * @retval -RED_EISDIR Expected type is file, actual type is directory. * @retval -RED_ENOTDIR Expected type is directory, actual type is file. */ static REDSTATUS ModeTypeCheck( uint16_t uMode, FTYPE expectedType ) { REDSTATUS ret; if( ( expectedType == FTYPE_FILE ) && RED_S_ISDIR( uMode ) ) { /* Expected file, found directory. */ ret = -RED_EISDIR; } else if( ( expectedType == FTYPE_DIR ) && RED_S_ISREG( uMode ) ) { /* Expected directory, found file. */ ret = -RED_ENOTDIR; } else { /* No expected type or found what we expected. */ ret = 0; } return ret; } #if ( REDCONF_READ_ONLY == 0 ) && ( ( REDCONF_API_POSIX_UNLINK == 1 ) || ( REDCONF_API_POSIX_RMDIR == 1 ) || ( ( REDCONF_API_POSIX_RENAME == 1 ) && ( REDCONF_RENAME_ATOMIC == 1 ) ) ) /** @brief Check whether an inode can be unlinked. * * If an inode has a link count of 1 (meaning unlinking another name would * result in the deletion of the inode) and open handles, it cannot be deleted * since this would break open handles. * * @param ulInode The inode whose name is to be unlinked. * * @return A negated ::REDSTATUS code indicating the operation result. * * @retval 0 Operation was successful. * @retval -RED_EBADF @p ulInode is not a valid inode. * @retval -RED_EBUSY The inode has a link count of one and open handles. * @retval -RED_EIO A disk I/O error occurred. */ static REDSTATUS InodeUnlinkCheck( uint32_t ulInode ) { uint16_t uHandleIdx; REDSTATUS ret; #if REDCONF_API_POSIX_LINK == 0 ret = 0; #else REDSTAT InodeStat; ret = RedCoreStat( ulInode, &InodeStat ); /* We only need to check for open handles if the inode is down to its last * link. If it has multiple links, the inode will continue to exist, so * deleting the name will not break the open handles. */ if( ( ret == 0 ) && ( InodeStat.st_nlink == 1U ) ) #endif { for( uHandleIdx = 0U; uHandleIdx < REDCONF_HANDLE_COUNT; uHandleIdx++ ) { if( ( gaHandle[ uHandleIdx ].ulInode == ulInode ) && ( gaHandle[ uHandleIdx ].bVolNum == gbRedVolNum ) ) { ret = -RED_EBUSY; break; } } } return ret; } #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( ( REDCONF_API_POSIX_UNLINK == 1 ) || ( REDCONF_API_POSIX_RMDIR == 1 ) || ( ( REDCONF_API_POSIX_RENAME == 1 ) && ( REDCONF_RENAME_ATOMIC == 1 ) ) ) */ #if REDCONF_TASK_COUNT > 1U /** @brief Register a task as a file system user, if it is not already * registered as one. * * The caller must hold the FS mutex. * * @param pulTaskIdx On successful return, if non-NULL, populated with the * index of the task slot assigned to the calling task. * This is populated whether or not the task had already * been registered. * * @return A negated ::REDSTATUS code indicating the operation result. * * @retval 0 Operation was successful. * @retval -RED_EUSERS Cannot become a file system user: too many users. */ static REDSTATUS TaskRegister( uint32_t * pulTaskIdx ) { uint32_t ulTaskId = RedOsTaskId(); uint32_t ulFirstFreeIdx = REDCONF_TASK_COUNT; uint32_t ulIdx; REDSTATUS ret; REDASSERT( ulTaskId != 0U ); /* Scan the task slots to determine if the task is registered as a file * system task. */ for( ulIdx = 0U; ulIdx < REDCONF_TASK_COUNT; ulIdx++ ) { if( gaTask[ ulIdx ].ulTaskId == ulTaskId ) { break; } if( ( ulFirstFreeIdx == REDCONF_TASK_COUNT ) && ( gaTask[ ulIdx ].ulTaskId == 0U ) ) { ulFirstFreeIdx = ulIdx; } } if( ulIdx == REDCONF_TASK_COUNT ) { /* Task not already registered. */ if( ulFirstFreeIdx == REDCONF_TASK_COUNT ) { /* Cannot register task, no more slots. */ ret = -RED_EUSERS; } else { /* Registering task. */ ulIdx = ulFirstFreeIdx; gaTask[ ulIdx ].ulTaskId = ulTaskId; ret = 0; } } else { /* Task already registered. */ ret = 0; } if( ( ret == 0 ) && ( pulTaskIdx != NULL ) ) { *pulTaskIdx = ulIdx; } return ret; } #endif /* REDCONF_TASK_COUNT > 1U */ /** @brief Convert an error value into a simple 0 or -1 return. * * This function is simple, but what it does is needed in many places. It * returns zero if @p iError is zero (meaning success) or it returns -1 if * @p iError is nonzero (meaning error). Also, if @p iError is nonzero, it * is saved in red_errno. * * @param iError The error value. * * @return Returns 0 if @p iError is 0; otherwise, returns -1. */ static int32_t PosixReturn( REDSTATUS iError ) { int32_t iReturn; if( iError == 0 ) { iReturn = 0; } else { iReturn = -1; /* The errors should be negative, and errno positive. */ REDASSERT( iError < 0 ); red_errno = -iError; } return iReturn; } #endif /* REDCONF_API_POSIX == 1 */