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