/* ----> 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 /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_FSE_FORMAT == 1 ) */ /** @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 */ #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 /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_FSE_TRUNCATE == 1 ) */ /** @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_READ_ONLY == 0 ) && ( REDCONF_API_FSE_TRANSMASKSET == 1 ) */ #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_API_FSE_TRANSMASKGET == 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 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 /* if REDCONF_READ_ONLY == 0 */ /** @} */ /** @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 */