/* ----> 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 Reliance Edge FSE API. */ #include #if REDCONF_API_FSE == 1 /** @defgroup red_group_fse The File System Essentials Interface @{ */ #include #include #include static REDSTATUS FseEnter(uint8_t bVolNum); static void FseLeave(void); static bool gfFseInited; /* Whether driver is initialized. */ /** @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 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 A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. */ REDSTATUS RedFseInit(void) { REDSTATUS ret; if(gfFseInited) { ret = 0; } else { ret = RedCoreInit(); if(ret == 0) { gfFseInited = true; } } return 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 RedFseInit() 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 A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EBUSY At least one volume is still mounted. */ REDSTATUS RedFseUninit(void) { REDSTATUS ret = 0; if(!gfFseInited) { ret = 0; } else { uint8_t bVolNum; #if REDCONF_TASK_COUNT > 1U RedOsMutexAcquire(); #endif for(bVolNum = 0U; bVolNum < REDCONF_VOLUME_COUNT; bVolNum++) { if(gaRedVolume[bVolNum].fMounted) { ret = -RED_EBUSY; break; } } if(ret == 0) { gfFseInited = false; } #if REDCONF_TASK_COUNT > 1U RedOsMutexRelease(); #endif if(ret == 0) { ret = RedCoreUninit(); } } return 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. If the volume is already mounted, this function does nothing and returns success. @param bVolNum The volume number of the volume to be mounted. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p bVolNum is an invalid volume number; or the driver is uninitialized. @retval -RED_EIO Volume not formatted, improperly formatted, or corrupt. */ REDSTATUS RedFseMount( uint8_t bVolNum) { REDSTATUS ret; ret = FseEnter(bVolNum); if(ret == 0) { if(!gpRedVolume->fMounted) { ret = RedCoreVolMount(); } FseLeave(); } return 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 is already unmounted, this function does nothing and returns success. @param bVolNum The volume number of the volume to be unmounted. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p bVolNum is an invalid volume number; or the driver is uninitialized. @retval -RED_EIO I/O error during unmount automatic transaction point. */ REDSTATUS RedFseUnmount( uint8_t bVolNum) { REDSTATUS ret; ret = FseEnter(bVolNum); if(ret == 0) { if(gpRedVolume->fMounted) { ret = RedCoreVolUnmount(); } FseLeave(); } return ret; } #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_FSE_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 RedFseMount(). An error is returned if the volume is mounted. @param bVolNum The volume number of the volume to be formatted. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EBUSY The volume is mounted. @retval -RED_EINVAL @p bVolNum is an invalid volume number; or the driver is uninitialized. @retval -RED_EIO I/O error formatting the volume. */ REDSTATUS RedFseFormat( uint8_t bVolNum) { REDSTATUS ret; ret = FseEnter(bVolNum); if(ret == 0) { ret = RedCoreVolFormat(); FseLeave(); } return ret; } #endif /** @brief Read from a file. Data which has not yet been written, but which is before the end-of-file (sparse data), shall 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. If @p ullFileOffset is at or beyond the maximum file size, it is treated like any other read entirely beyond the end-of-file: no data is read and zero is returned. @param bVolNum The volume number of the file to read. @param ulFileNum The file number of the file to read. @param ullFileOffset The file offset to read from. @param ulLength The number of bytes to read. @param pBuffer The buffer to populate with the data read. Must be at least ulLength bytes in size. @return The number of bytes read (nonnegative) or a negated ::REDSTATUS code indicating the operation result (negative). @retval >=0 The number of bytes read from the file. @retval -RED_EBADF @p ulFileNum is not a valid file number. @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted; or @p pBuffer is `NULL`; or @p ulLength exceeds INT32_MAX and cannot be returned properly. @retval -RED_EIO A disk I/O error occurred. */ int32_t RedFseRead( uint8_t bVolNum, uint32_t ulFileNum, uint64_t ullFileOffset, uint32_t ulLength, void *pBuffer) { int32_t ret; if(ulLength > (uint32_t)INT32_MAX) { ret = -RED_EINVAL; } else { ret = FseEnter(bVolNum); } if(ret == 0) { uint32_t ulReadLen = ulLength; ret = RedCoreFileRead(ulFileNum, ullFileOffset, &ulReadLen, pBuffer); FseLeave(); if(ret == 0) { ret = (int32_t)ulReadLen; } } return ret; } #if REDCONF_READ_ONLY == 0 /** @brief Write to a file. If the write extends beyond the end-of-file, the file size will be increased. 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 (negative return), 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 bVolNum The volume number of the file to write. @param ulFileNum The file number of the file to write. @param ullFileOffset The file offset to write at. @param ulLength The number of bytes to write. @param pBuffer The buffer containing the data to be written. Must be at least ulLength bytes in size. @return The number of bytes written (nonnegative) or a negated ::REDSTATUS code indicating the operation result (negative). @retval >0 The number of bytes written to the file. @retval -RED_EBADF @p ulFileNum is not a valid file number. @retval -RED_EFBIG No data can be written to the given file offset since the resulting file size would exceed the maximum file size. @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted; or @p pBuffer is `NULL`; or @p ulLength exceeds INT32_MAX and cannot be returned properly. @retval -RED_EIO A disk I/O error occurred. @retval -RED_ENOSPC No data can be written because there is insufficient free space. @retval -RED_EROFS The file system volume is read-only. */ int32_t RedFseWrite( uint8_t bVolNum, uint32_t ulFileNum, uint64_t ullFileOffset, uint32_t ulLength, const void *pBuffer) { int32_t ret; if(ulLength > (uint32_t)INT32_MAX) { ret = -RED_EINVAL; } else { ret = FseEnter(bVolNum); } if(ret == 0) { uint32_t ulWriteLen = ulLength; ret = RedCoreFileWrite(ulFileNum, ullFileOffset, &ulWriteLen, pBuffer); FseLeave(); if(ret == 0) { ret = (int32_t)ulWriteLen; } } return ret; } #endif #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_FSE_TRUNCATE == 1) /** @brief Truncate a file (set the file size). 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). This function can fail when the disk is full if @p ullNewFileSize 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 bVolNum The volume number of the file to truncate. @param ulFileNum The file number of the file to truncate. @param ullNewFileSize The new file size, in bytes. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EBADF @p ulFileNum is not a valid file number. @retval -RED_EFBIG @p ullNewFileSize exceeds the maximum file size. @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted. @retval -RED_EIO A disk I/O error occurred. @retval -RED_ENOSPC Insufficient free space to perform the truncate. @retval -RED_EROFS The file system volume is read-only. */ REDSTATUS RedFseTruncate( uint8_t bVolNum, uint32_t ulFileNum, uint64_t ullNewFileSize) { REDSTATUS ret; ret = FseEnter(bVolNum); if(ret == 0) { ret = RedCoreFileTruncate(ulFileNum, ullNewFileSize); FseLeave(); } return ret; } #endif /** @brief Retrieve the size of a file. @param bVolNum The volume number of the file whose size is being read. @param ulFileNum The file number of the file whose size is being read. @return The size of the file (nonnegative) or a negated ::REDSTATUS code indicating the operation result (negative). @retval >=0 The size of the file. @retval -RED_EBADF @p ulFileNum is not a valid file number. @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted. @retval -RED_EIO A disk I/O error occurred. */ int64_t RedFseSizeGet( uint8_t bVolNum, uint32_t ulFileNum) { int64_t ret; ret = FseEnter(bVolNum); if(ret == 0) { uint64_t ullSize; ret = RedCoreFileSizeGet(ulFileNum, &ullSize); FseLeave(); if(ret == 0) { /* Unless there is an on-disk format change, the maximum file size is guaranteed to be less than INT64_MAX, and so it can be safely returned in an int64_t. */ REDASSERT(ullSize < (uint64_t)INT64_MAX); ret = (int64_t)ullSize; } } return ret; } #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_FSE_TRANSMASKSET == 1) /** @brief Update the transaction mask. The following events are available: - #RED_TRANSACT_UMOUNT - #RED_TRANSACT_WRITE - #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 bVolNum The volume number 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 A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted; or @p ulEventMask contains invalid bits. @retval -RED_EROFS The file system volume is read-only. */ REDSTATUS RedFseTransMaskSet( uint8_t bVolNum, uint32_t ulEventMask) { REDSTATUS ret; ret = FseEnter(bVolNum); if(ret == 0) { ret = RedCoreTransMaskSet(ulEventMask); FseLeave(); } return ret; } #endif #if REDCONF_API_FSE_TRANSMASKGET == 1 /** @brief Read the transaction mask. If the volume is read-only, the returned event mask is always zero. @param bVolNum The volume number 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 A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted; or @p pulEventMask is `NULL`. */ REDSTATUS RedFseTransMaskGet( uint8_t bVolNum, uint32_t *pulEventMask) { REDSTATUS ret; ret = FseEnter(bVolNum); if(ret == 0) { ret = RedCoreTransMaskGet(pulEventMask); FseLeave(); } return 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 bVolNum The volume number of the volume to transact. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted. @retval -RED_EIO A disk I/O error occurred. @retval -RED_EROFS The file system volume is read-only. */ REDSTATUS RedFseTransact( uint8_t bVolNum) { REDSTATUS ret; ret = FseEnter(bVolNum); if(ret == 0) { ret = RedCoreVolTransact(); FseLeave(); } return ret; } #endif /** @} */ /** @brief Enter the file system driver. @param bVolNum The volume to be accessed. @return A negated ::REDSTATUS code indicating the operation result. @retval 0 Operation was successful. @retval -RED_EINVAL The file system driver is uninitialized; or @p bVolNum is not a valid volume number. */ static REDSTATUS FseEnter( uint8_t bVolNum) { REDSTATUS ret; if(gfFseInited) { #if REDCONF_TASK_COUNT > 1U RedOsMutexAcquire(); #endif /* This also serves to range-check the volume number (even in single volume configurations). */ ret = RedCoreVolSetCurrent(bVolNum); #if REDCONF_TASK_COUNT > 1U if(ret != 0) { RedOsMutexRelease(); } #endif } else { ret = -RED_EINVAL; } return ret; } /** @brief Leave the file system driver. */ static void FseLeave(void) { REDASSERT(gfFseInited); #if REDCONF_TASK_COUNT > 1U RedOsMutexRelease(); #endif } #endif /* REDCONF_API_FSE == 1 */