1 /* ----> DO NOT REMOVE THE FOLLOWING NOTICE <---- 2 * 3 * Copyright (c) 2014-2015 Datalight, Inc. 4 * All Rights Reserved Worldwide. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; use version 2 of the License. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty 12 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 20 /* Businesses and individuals that for commercial or other reasons cannot 21 * comply with the terms of the GPLv2 license may obtain a commercial license 22 * before incorporating Reliance Edge into proprietary software for 23 * distribution in any form. Visit http://www.datalight.com/reliance-edge for 24 * more information. 25 */ 26 27 /** @file 28 * @brief Implementation of the Reliance Edge FSE API. 29 */ 30 #include <redfs.h> 31 32 #if REDCONF_API_FSE == 1 33 34 /** @defgroup red_group_fse The File System Essentials Interface 35 * @{ 36 */ 37 38 #include <redvolume.h> 39 #include <redcoreapi.h> 40 #include <redfse.h> 41 42 43 static REDSTATUS FseEnter( uint8_t bVolNum ); 44 static void FseLeave( void ); 45 46 47 static bool gfFseInited; /* Whether driver is initialized. */ 48 49 50 /** @brief Initialize the Reliance Edge file system driver. 51 * 52 * Prepares the Reliance Edge file system driver to be used. Must be the first 53 * Reliance Edge function to be invoked: no volumes can be mounted until the 54 * driver has been initialized. 55 * 56 * If this function is called when the Reliance Edge driver is already 57 * initialized, it does nothing and returns success. 58 * 59 * This function is not thread safe: attempting to initialize from multiple 60 * threads could leave things in a bad state. 61 * 62 * @return A negated ::REDSTATUS code indicating the operation result. 63 * 64 * @retval 0 Operation was successful. 65 */ RedFseInit(void)66 REDSTATUS RedFseInit( void ) 67 { 68 REDSTATUS ret; 69 70 if( gfFseInited ) 71 { 72 ret = 0; 73 } 74 else 75 { 76 ret = RedCoreInit(); 77 78 if( ret == 0 ) 79 { 80 gfFseInited = true; 81 } 82 } 83 84 return ret; 85 } 86 87 88 /** @brief Uninitialize the Reliance Edge file system driver. 89 * 90 * Tears down the Reliance Edge file system driver. Cannot be used until all 91 * Reliance Edge volumes are unmounted. A subsequent call to RedFseInit() 92 * will initialize the driver again. 93 * 94 * If this function is called when the Reliance Edge driver is already 95 * uninitialized, it does nothing and returns success. 96 * 97 * This function is not thread safe: attempting to uninitialize from multiple 98 * threads could leave things in a bad state. 99 * 100 * @return A negated ::REDSTATUS code indicating the operation result. 101 * 102 * @retval 0 Operation was successful. 103 * @retval -RED_EBUSY At least one volume is still mounted. 104 */ RedFseUninit(void)105 REDSTATUS RedFseUninit( void ) 106 { 107 REDSTATUS ret = 0; 108 109 if( !gfFseInited ) 110 { 111 ret = 0; 112 } 113 else 114 { 115 uint8_t bVolNum; 116 117 #if REDCONF_TASK_COUNT > 1U 118 RedOsMutexAcquire(); 119 #endif 120 121 for( bVolNum = 0U; bVolNum < REDCONF_VOLUME_COUNT; bVolNum++ ) 122 { 123 if( gaRedVolume[ bVolNum ].fMounted ) 124 { 125 ret = -RED_EBUSY; 126 break; 127 } 128 } 129 130 if( ret == 0 ) 131 { 132 gfFseInited = false; 133 } 134 135 #if REDCONF_TASK_COUNT > 1U 136 RedOsMutexRelease(); 137 #endif 138 139 if( ret == 0 ) 140 { 141 ret = RedCoreUninit(); 142 } 143 } 144 145 return ret; 146 } 147 148 149 /** @brief Mount a file system volume. 150 * 151 * Prepares the file system volume to be accessed. Mount will fail if the 152 * volume has never been formatted, or if the on-disk format is inconsistent 153 * with the compile-time configuration. 154 * 155 * If the volume is already mounted, this function does nothing and returns 156 * success. 157 * 158 * @param bVolNum The volume number of the volume to be mounted. 159 * 160 * @return A negated ::REDSTATUS code indicating the operation result. 161 * 162 * @retval 0 Operation was successful. 163 * @retval -RED_EINVAL @p bVolNum is an invalid volume number; or the driver is 164 * uninitialized. 165 * @retval -RED_EIO Volume not formatted, improperly formatted, or corrupt. 166 */ RedFseMount(uint8_t bVolNum)167 REDSTATUS RedFseMount( uint8_t bVolNum ) 168 { 169 REDSTATUS ret; 170 171 ret = FseEnter( bVolNum ); 172 173 if( ret == 0 ) 174 { 175 if( !gpRedVolume->fMounted ) 176 { 177 ret = RedCoreVolMount(); 178 } 179 180 FseLeave(); 181 } 182 183 return ret; 184 } 185 186 187 /** @brief Unmount a file system volume. 188 * 189 * This function discards the in-memory state for the file system and marks it 190 * as unmounted. Subsequent attempts to access the volume will fail until the 191 * volume is mounted again. 192 * 193 * If unmount automatic transaction points are enabled, this function will 194 * commit a transaction point prior to unmounting. If unmount automatic 195 * transaction points are disabled, this function will unmount without 196 * transacting, effectively discarding the working state. 197 * 198 * Before unmounting, this function will wait for any active file system 199 * thread to complete by acquiring the FS mutex. The volume will be marked as 200 * unmounted before the FS mutex is released, so subsequent FS threads will 201 * possibly block and then see an error when attempting to access a volume 202 * which is unmounting or unmounted. 203 * 204 * If the volume is already unmounted, this function does nothing and returns 205 * success. 206 * 207 * @param bVolNum The volume number of the volume to be unmounted. 208 * 209 * @return A negated ::REDSTATUS code indicating the operation result. 210 * 211 * @retval 0 Operation was successful. 212 * @retval -RED_EINVAL @p bVolNum is an invalid volume number; or the driver is 213 * uninitialized. 214 * @retval -RED_EIO I/O error during unmount automatic transaction point. 215 */ RedFseUnmount(uint8_t bVolNum)216 REDSTATUS RedFseUnmount( uint8_t bVolNum ) 217 { 218 REDSTATUS ret; 219 220 ret = FseEnter( bVolNum ); 221 222 if( ret == 0 ) 223 { 224 if( gpRedVolume->fMounted ) 225 { 226 ret = RedCoreVolUnmount(); 227 } 228 229 FseLeave(); 230 } 231 232 return ret; 233 } 234 235 236 #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_FSE_FORMAT == 1 ) 237 238 /** @brief Format a file system volume. 239 * 240 * Uses the statically defined volume configuration. After calling this 241 * function, the volume needs to be mounted -- see RedFseMount(). 242 * 243 * An error is returned if the volume is mounted. 244 * 245 * @param bVolNum The volume number of the volume to be formatted. 246 * 247 * @return A negated ::REDSTATUS code indicating the operation result. 248 * 249 * @retval 0 Operation was successful. 250 * @retval -RED_EBUSY The volume is mounted. 251 * @retval -RED_EINVAL @p bVolNum is an invalid volume number; or the driver is 252 * uninitialized. 253 * @retval -RED_EIO I/O error formatting the volume. 254 */ RedFseFormat(uint8_t bVolNum)255 REDSTATUS RedFseFormat( uint8_t bVolNum ) 256 { 257 REDSTATUS ret; 258 259 ret = FseEnter( bVolNum ); 260 261 if( ret == 0 ) 262 { 263 ret = RedCoreVolFormat(); 264 265 FseLeave(); 266 } 267 268 return ret; 269 } 270 #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_FSE_FORMAT == 1 ) */ 271 272 273 /** @brief Read from a file. 274 * 275 * Data which has not yet been written, but which is before the end-of-file 276 * (sparse data), shall read as zeroes. A short read -- where the number of 277 * bytes read is less than requested -- indicates that the requested read was 278 * partially or, if zero bytes were read, entirely beyond the end-of-file. 279 * 280 * If @p ullFileOffset is at or beyond the maximum file size, it is treated 281 * like any other read entirely beyond the end-of-file: no data is read and 282 * zero is returned. 283 * 284 * @param bVolNum The volume number of the file to read. 285 * @param ulFileNum The file number of the file to read. 286 * @param ullFileOffset The file offset to read from. 287 * @param ulLength The number of bytes to read. 288 * @param pBuffer The buffer to populate with the data read. Must be 289 * at least ulLength bytes in size. 290 * 291 * @return The number of bytes read (nonnegative) or a negated ::REDSTATUS 292 * code indicating the operation result (negative). 293 * 294 * @retval >=0 The number of bytes read from the file. 295 * @retval -RED_EBADF @p ulFileNum is not a valid file number. 296 * @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted; 297 * or @p pBuffer is `NULL`; or @p ulLength exceeds 298 * INT32_MAX and cannot be returned properly. 299 * @retval -RED_EIO A disk I/O error occurred. 300 */ RedFseRead(uint8_t bVolNum,uint32_t ulFileNum,uint64_t ullFileOffset,uint32_t ulLength,void * pBuffer)301 int32_t RedFseRead( uint8_t bVolNum, 302 uint32_t ulFileNum, 303 uint64_t ullFileOffset, 304 uint32_t ulLength, 305 void * pBuffer ) 306 { 307 int32_t ret; 308 309 if( ulLength > ( uint32_t ) INT32_MAX ) 310 { 311 ret = -RED_EINVAL; 312 } 313 else 314 { 315 ret = FseEnter( bVolNum ); 316 } 317 318 if( ret == 0 ) 319 { 320 uint32_t ulReadLen = ulLength; 321 322 ret = RedCoreFileRead( ulFileNum, ullFileOffset, &ulReadLen, pBuffer ); 323 324 FseLeave(); 325 326 if( ret == 0 ) 327 { 328 ret = ( int32_t ) ulReadLen; 329 } 330 } 331 332 return ret; 333 } 334 335 336 #if REDCONF_READ_ONLY == 0 337 338 /** @brief Write to a file. 339 * 340 * If the write extends beyond the end-of-file, the file size will be 341 * increased. 342 * 343 * A short write -- where the number of bytes written is less than requested 344 * -- indicates either that the file system ran out of space but was still 345 * able to write some of the request; or that the request would have caused 346 * the file to exceed the maximum file size, but some of the data could be 347 * written prior to the file size limit. 348 * 349 * If an error is returned (negative return), either none of the data was 350 * written or a critical error occurred (like an I/O error) and the file 351 * system volume will be read-only. 352 * 353 * @param bVolNum The volume number of the file to write. 354 * @param ulFileNum The file number of the file to write. 355 * @param ullFileOffset The file offset to write at. 356 * @param ulLength The number of bytes to write. 357 * @param pBuffer The buffer containing the data to be written. Must 358 * be at least ulLength bytes in size. 359 * 360 * @return The number of bytes written (nonnegative) or a negated ::REDSTATUS 361 * code indicating the operation result (negative). 362 * 363 * @retval >0 The number of bytes written to the file. 364 * @retval -RED_EBADF @p ulFileNum is not a valid file number. 365 * @retval -RED_EFBIG No data can be written to the given file offset since 366 * the resulting file size would exceed the maximum file 367 * size. 368 * @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted; 369 * or @p pBuffer is `NULL`; or @p ulLength exceeds 370 * INT32_MAX and cannot be returned properly. 371 * @retval -RED_EIO A disk I/O error occurred. 372 * @retval -RED_ENOSPC No data can be written because there is insufficient 373 * free space. 374 * @retval -RED_EROFS The file system volume is read-only. 375 */ RedFseWrite(uint8_t bVolNum,uint32_t ulFileNum,uint64_t ullFileOffset,uint32_t ulLength,const void * pBuffer)376 int32_t RedFseWrite( uint8_t bVolNum, 377 uint32_t ulFileNum, 378 uint64_t ullFileOffset, 379 uint32_t ulLength, 380 const void * pBuffer ) 381 { 382 int32_t ret; 383 384 if( ulLength > ( uint32_t ) INT32_MAX ) 385 { 386 ret = -RED_EINVAL; 387 } 388 else 389 { 390 ret = FseEnter( bVolNum ); 391 } 392 393 if( ret == 0 ) 394 { 395 uint32_t ulWriteLen = ulLength; 396 397 ret = RedCoreFileWrite( ulFileNum, ullFileOffset, &ulWriteLen, pBuffer ); 398 399 FseLeave(); 400 401 if( ret == 0 ) 402 { 403 ret = ( int32_t ) ulWriteLen; 404 } 405 } 406 407 return ret; 408 } 409 #endif /* if REDCONF_READ_ONLY == 0 */ 410 411 412 #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_FSE_TRUNCATE == 1 ) 413 414 /** @brief Truncate a file (set the file size). 415 * 416 * Allows the file size to be increased, decreased, or to remain the same. If 417 * the file size is increased, the new area is sparse (will read as zeroes). 418 * If the file size is decreased, the data beyond the new end-of-file will 419 * return to free space once it is no longer part of the committed state 420 * (either immediately or after the next transaction point). 421 * 422 * This function can fail when the disk is full if @p ullNewFileSize is 423 * non-zero. If decreasing the file size, this can be fixed by transacting and 424 * trying again: Reliance Edge guarantees that it is possible to perform a 425 * truncate of at least one file that decreases the file size after a 426 * transaction point. If disk full transactions are enabled, this will happen 427 * automatically. 428 * 429 * @param bVolNum The volume number of the file to truncate. 430 * @param ulFileNum The file number of the file to truncate. 431 * @param ullNewFileSize The new file size, in bytes. 432 * 433 * @return A negated ::REDSTATUS code indicating the operation result. 434 * 435 * @retval 0 Operation was successful. 436 * @retval -RED_EBADF @p ulFileNum is not a valid file number. 437 * @retval -RED_EFBIG @p ullNewFileSize exceeds the maximum file size. 438 * @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted. 439 * @retval -RED_EIO A disk I/O error occurred. 440 * @retval -RED_ENOSPC Insufficient free space to perform the truncate. 441 * @retval -RED_EROFS The file system volume is read-only. 442 */ RedFseTruncate(uint8_t bVolNum,uint32_t ulFileNum,uint64_t ullNewFileSize)443 REDSTATUS RedFseTruncate( uint8_t bVolNum, 444 uint32_t ulFileNum, 445 uint64_t ullNewFileSize ) 446 { 447 REDSTATUS ret; 448 449 ret = FseEnter( bVolNum ); 450 451 if( ret == 0 ) 452 { 453 ret = RedCoreFileTruncate( ulFileNum, ullNewFileSize ); 454 455 FseLeave(); 456 } 457 458 return ret; 459 } 460 #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_FSE_TRUNCATE == 1 ) */ 461 462 463 /** @brief Retrieve the size of a file. 464 * 465 * @param bVolNum The volume number of the file whose size is being read. 466 * @param ulFileNum The file number of the file whose size is being read. 467 * 468 * @return The size of the file (nonnegative) or a negated ::REDSTATUS code 469 * indicating the operation result (negative). 470 * 471 * @retval >=0 The size of the file. 472 * @retval -RED_EBADF @p ulFileNum is not a valid file number. 473 * @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted. 474 * @retval -RED_EIO A disk I/O error occurred. 475 */ RedFseSizeGet(uint8_t bVolNum,uint32_t ulFileNum)476 int64_t RedFseSizeGet( uint8_t bVolNum, 477 uint32_t ulFileNum ) 478 { 479 int64_t ret; 480 481 ret = FseEnter( bVolNum ); 482 483 if( ret == 0 ) 484 { 485 uint64_t ullSize; 486 487 ret = RedCoreFileSizeGet( ulFileNum, &ullSize ); 488 489 FseLeave(); 490 491 if( ret == 0 ) 492 { 493 /* Unless there is an on-disk format change, the maximum file size 494 * is guaranteed to be less than INT64_MAX, and so it can be safely 495 * returned in an int64_t. 496 */ 497 REDASSERT( ullSize < ( uint64_t ) INT64_MAX ); 498 499 ret = ( int64_t ) ullSize; 500 } 501 } 502 503 return ret; 504 } 505 506 507 #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_FSE_TRANSMASKSET == 1 ) 508 509 /** @brief Update the transaction mask. 510 * 511 * The following events are available: 512 * 513 * - #RED_TRANSACT_UMOUNT 514 * - #RED_TRANSACT_WRITE 515 * - #RED_TRANSACT_TRUNCATE 516 * - #RED_TRANSACT_VOLFULL 517 * 518 * The #RED_TRANSACT_MANUAL macro (by itself) may be used to disable all 519 * automatic transaction events. The #RED_TRANSACT_MASK macro is a bitmask of 520 * all transaction flags, excluding those representing excluded functionality. 521 * 522 * Attempting to enable events for excluded functionality will result in an 523 * error. 524 * 525 * @param bVolNum The volume number of the volume whose transaction mask 526 * is being changed. 527 * @param ulEventMask A bitwise-OR'd mask of automatic transaction events to 528 * be set as the current transaction mode. 529 * 530 * @return A negated ::REDSTATUS code indicating the operation result. 531 * 532 * @retval 0 Operation was successful. 533 * @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted; 534 * or @p ulEventMask contains invalid bits. 535 * @retval -RED_EROFS The file system volume is read-only. 536 */ RedFseTransMaskSet(uint8_t bVolNum,uint32_t ulEventMask)537 REDSTATUS RedFseTransMaskSet( uint8_t bVolNum, 538 uint32_t ulEventMask ) 539 { 540 REDSTATUS ret; 541 542 ret = FseEnter( bVolNum ); 543 544 if( ret == 0 ) 545 { 546 ret = RedCoreTransMaskSet( ulEventMask ); 547 548 FseLeave(); 549 } 550 551 return ret; 552 } 553 #endif /* if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_FSE_TRANSMASKSET == 1 ) */ 554 555 556 #if REDCONF_API_FSE_TRANSMASKGET == 1 557 558 /** @brief Read the transaction mask. 559 * 560 * If the volume is read-only, the returned event mask is always zero. 561 * 562 * @param bVolNum The volume number of the volume whose transaction mask 563 * is being retrieved. 564 * @param pulEventMask Populated with a bitwise-OR'd mask of automatic 565 * transaction events which represent the current 566 * transaction mode for the volume. 567 * 568 * @return A negated ::REDSTATUS code indicating the operation result. 569 * 570 * @retval 0 Operation was successful. 571 * @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted; 572 * or @p pulEventMask is `NULL`. 573 */ RedFseTransMaskGet(uint8_t bVolNum,uint32_t * pulEventMask)574 REDSTATUS RedFseTransMaskGet( uint8_t bVolNum, 575 uint32_t * pulEventMask ) 576 { 577 REDSTATUS ret; 578 579 ret = FseEnter( bVolNum ); 580 581 if( ret == 0 ) 582 { 583 ret = RedCoreTransMaskGet( pulEventMask ); 584 585 FseLeave(); 586 } 587 588 return ret; 589 } 590 #endif /* if REDCONF_API_FSE_TRANSMASKGET == 1 */ 591 592 593 #if REDCONF_READ_ONLY == 0 594 595 /** @brief Commit a transaction point. 596 * 597 * Reliance Edge is a transactional file system. All modifications, of both 598 * metadata and filedata, are initially working state. A transaction point 599 * is a process whereby the working state atomically becomes the committed 600 * state, replacing the previous committed state. Whenever Reliance Edge is 601 * mounted, including after power loss, the state of the file system after 602 * mount is the most recent committed state. Nothing from the committed 603 * state is ever missing, and nothing from the working state is ever included. 604 * 605 * @param bVolNum The volume number of the volume to transact. 606 * 607 * @return A negated ::REDSTATUS code indicating the operation result. 608 * 609 * @retval 0 Operation was successful. 610 * @retval -RED_EINVAL @p bVolNum is an invalid volume number or not mounted. 611 * @retval -RED_EIO A disk I/O error occurred. 612 * @retval -RED_EROFS The file system volume is read-only. 613 */ RedFseTransact(uint8_t bVolNum)614 REDSTATUS RedFseTransact( uint8_t bVolNum ) 615 { 616 REDSTATUS ret; 617 618 ret = FseEnter( bVolNum ); 619 620 if( ret == 0 ) 621 { 622 ret = RedCoreVolTransact(); 623 624 FseLeave(); 625 } 626 627 return ret; 628 } 629 #endif /* if REDCONF_READ_ONLY == 0 */ 630 631 /** @} */ 632 633 /** @brief Enter the file system driver. 634 * 635 * @param bVolNum The volume to be accessed. 636 * 637 * @return A negated ::REDSTATUS code indicating the operation result. 638 * 639 * @retval 0 Operation was successful. 640 * @retval -RED_EINVAL The file system driver is uninitialized; or @p bVolNum 641 * is not a valid volume number. 642 */ FseEnter(uint8_t bVolNum)643 static REDSTATUS FseEnter( uint8_t bVolNum ) 644 { 645 REDSTATUS ret; 646 647 if( gfFseInited ) 648 { 649 #if REDCONF_TASK_COUNT > 1U 650 RedOsMutexAcquire(); 651 #endif 652 653 /* This also serves to range-check the volume number (even in single 654 * volume configurations). 655 */ 656 ret = RedCoreVolSetCurrent( bVolNum ); 657 658 #if REDCONF_TASK_COUNT > 1U 659 if( ret != 0 ) 660 { 661 RedOsMutexRelease(); 662 } 663 #endif 664 } 665 else 666 { 667 ret = -RED_EINVAL; 668 } 669 670 return ret; 671 } 672 673 674 /** @brief Leave the file system driver. 675 */ FseLeave(void)676 static void FseLeave( void ) 677 { 678 REDASSERT( gfFseInited ); 679 680 #if REDCONF_TASK_COUNT > 1U 681 RedOsMutexRelease(); 682 #endif 683 } 684 685 686 #endif /* REDCONF_API_FSE == 1 */ 687