| /* ----> 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 <redfs.h> | |
| #if REDCONF_API_FSE == 1 | |
| /** @defgroup red_group_fse The File System Essentials Interface | |
| @{ | |
| */ | |
| #include <redvolume.h> | |
| #include <redcoreapi.h> | |
| #include <redfse.h> | |
| 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 */ | |