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 Implements inode I/O functions.
29  */
30 #include <redfs.h>
31 #include <redcore.h>
32 
33 
34 /*  This value is used to initialize the uIndirEntry and uDindirEntry members of
35  *  the CINODE structure.  After seeking, a value of COORD_ENTRY_INVALID in
36  *  uIndirEntry indicates that there is no indirect node in the path through the
37  *  file metadata structure, and a value of COORD_ENTRY_INVALID in uDindirEntry
38  *  indicates that there is no double indirect node.
39  */
40 #define COORD_ENTRY_INVALID    ( UINT16_MAX )
41 
42 /*  This enumeration is used by the BranchBlock() and BranchBlockCost()
43  *  functions to determine which blocks of the file metadata structure need to
44  *  be branched, and which to ignore.  DINDIR requires requires branching the
45  *  double indirect only, INDIR requires branching the double indirect
46  *  (if present) and the indirect, and FILE_DATA requires branching the indirect
47  *  and double indirect (if present) and the file data block.
48  */
49 typedef enum
50 {
51     BRANCHDEPTH_DINDIR = 0U,
52     BRANCHDEPTH_INDIR = 1U,
53     BRANCHDEPTH_FILE_DATA = 2U,
54     BRANCHDEPTH_MAX = BRANCHDEPTH_FILE_DATA
55 } BRANCHDEPTH;
56 
57 
58 #if REDCONF_READ_ONLY == 0
59     #if DELETE_SUPPORTED || TRUNCATE_SUPPORTED
60         static REDSTATUS Shrink( CINODE * pInode,
61                                  uint64_t ullSize );
62         #if DINDIR_POINTERS > 0U
63             static REDSTATUS TruncDindir( CINODE * pInode,
64                                           bool * pfFreed );
65         #endif
66         #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
67             static REDSTATUS TruncIndir( CINODE * pInode,
68                                          bool * pfFreed );
69         #endif
70         static REDSTATUS TruncDataBlock( const CINODE * pInode,
71                                          uint32_t * pulBlock,
72                                          bool fPropagate );
73     #endif /* if DELETE_SUPPORTED || TRUNCATE_SUPPORTED */
74     static REDSTATUS ExpandPrepare( CINODE * pInode );
75 #endif /* if REDCONF_READ_ONLY == 0 */
76 static void SeekCoord( CINODE * pInode,
77                        uint32_t ulBlock );
78 static REDSTATUS ReadUnaligned( CINODE * pInode,
79                                 uint64_t ullStart,
80                                 uint32_t ulLen,
81                                 uint8_t * pbBuffer );
82 static REDSTATUS ReadAligned( CINODE * pInode,
83                               uint32_t ulBlockStart,
84                               uint32_t ulBlockCount,
85                               uint8_t * pbBuffer );
86 #if REDCONF_READ_ONLY == 0
87     static REDSTATUS WriteUnaligned( CINODE * pInode,
88                                      uint64_t ullStart,
89                                      uint32_t ulLen,
90                                      const uint8_t * pbBuffer );
91     static REDSTATUS WriteAligned( CINODE * pInode,
92                                    uint32_t ulBlockStart,
93                                    uint32_t * pulBlockCount,
94                                    const uint8_t * pbBuffer );
95 #endif
96 static REDSTATUS GetExtent( CINODE * pInode,
97                             uint32_t ulBlockStart,
98                             uint32_t * pulExtentStart,
99                             uint32_t * pulExtentLen );
100 #if REDCONF_READ_ONLY == 0
101     static REDSTATUS BranchBlock( CINODE * pInode,
102                                   BRANCHDEPTH depth,
103                                   bool fBuffer );
104     static REDSTATUS BranchOneBlock( uint32_t * pulBlock,
105                                      void ** ppBuffer,
106                                      uint16_t uBFlag );
107     static REDSTATUS BranchBlockCost( const CINODE * pInode,
108                                       BRANCHDEPTH depth,
109                                       uint32_t * pulCost );
110     static uint32_t FreeBlockCount( void );
111 #endif /* if REDCONF_READ_ONLY == 0 */
112 
113 
114 /** @brief Read data from an inode.
115  *
116  *  @param pInode   A pointer to the cached inode structure of the inode from
117  *                  which to read.
118  *  @param ullStart The file offset at which to read.
119  *  @param pulLen   On input, the number of bytes to attempt to read.  On
120  *                  successful return, populated with the number of bytes
121  *                  actually read.
122  *  @param pBuffer  The buffer to read into.
123  *
124  *  @return A negated ::REDSTATUS code indicating the operation result.
125  *
126  *  @retval 0           Operation was successful.
127  *  @retval -RED_EIO    A disk I/O error occurred.
128  *  @retval -RED_EINVAL @p pInode is not a mounted cached inode pointer; or
129  *                      @p pulLen is `NULL`; or @p pBuffer is `NULL`.
130  */
RedInodeDataRead(CINODE * pInode,uint64_t ullStart,uint32_t * pulLen,void * pBuffer)131 REDSTATUS RedInodeDataRead( CINODE * pInode,
132                             uint64_t ullStart,
133                             uint32_t * pulLen,
134                             void * pBuffer )
135 {
136     REDSTATUS ret = 0;
137 
138     if( !CINODE_IS_MOUNTED( pInode ) || ( pulLen == NULL ) || ( pBuffer == NULL ) )
139     {
140         ret = -RED_EINVAL;
141     }
142     else if( ullStart >= pInode->pInodeBuf->ullSize )
143     {
144         *pulLen = 0U;
145     }
146     else if( *pulLen == 0U )
147     {
148         /*  Do nothing, just return success.
149          */
150     }
151     else
152     {
153         uint8_t * pbBuffer = CAST_VOID_PTR_TO_UINT8_PTR( pBuffer );
154         uint32_t ulReadIndex = 0U;
155         uint32_t ulLen = *pulLen;
156         uint32_t ulRemaining;
157 
158         /*  Reading beyond the end of the file is not allowed.  If the requested
159          *  read extends beyond the end of the file, truncate the read length so
160          *  that the read stops at the end of the file.
161          */
162         if( ( pInode->pInodeBuf->ullSize - ullStart ) < ulLen )
163         {
164             ulLen = ( uint32_t ) ( pInode->pInodeBuf->ullSize - ullStart );
165         }
166 
167         ulRemaining = ulLen;
168 
169         /*  Unaligned partial block at start.
170          */
171         if( ( ullStart & ( REDCONF_BLOCK_SIZE - 1U ) ) != 0U )
172         {
173             uint32_t ulBytesInFirstBlock = REDCONF_BLOCK_SIZE - ( uint32_t ) ( ullStart & ( REDCONF_BLOCK_SIZE - 1U ) );
174             uint32_t ulThisRead = REDMIN( ulRemaining, ulBytesInFirstBlock );
175 
176             ret = ReadUnaligned( pInode, ullStart, ulThisRead, pbBuffer );
177 
178             if( ret == 0 )
179             {
180                 ulReadIndex += ulThisRead;
181                 ulRemaining -= ulThisRead;
182             }
183         }
184 
185         /*  Whole blocks.
186          */
187         if( ( ret == 0 ) && ( ulRemaining >= REDCONF_BLOCK_SIZE ) )
188         {
189             uint32_t ulBlockOffset = ( uint32_t ) ( ( ullStart + ulReadIndex ) >> BLOCK_SIZE_P2 );
190             uint32_t ulBlockCount = ulRemaining >> BLOCK_SIZE_P2;
191 
192             REDASSERT( ( ( ullStart + ulReadIndex ) & ( REDCONF_BLOCK_SIZE - 1U ) ) == 0U );
193 
194             ret = ReadAligned( pInode, ulBlockOffset, ulBlockCount, &pbBuffer[ ulReadIndex ] );
195 
196             if( ret == 0 )
197             {
198                 ulReadIndex += ulBlockCount << BLOCK_SIZE_P2;
199                 ulRemaining -= ulBlockCount << BLOCK_SIZE_P2;
200             }
201         }
202 
203         /*  Aligned partial block at end.
204          */
205         if( ( ret == 0 ) && ( ulRemaining > 0U ) )
206         {
207             REDASSERT( ulRemaining < REDCONF_BLOCK_SIZE );
208             REDASSERT( ( ( ullStart + ulReadIndex ) & ( REDCONF_BLOCK_SIZE - 1U ) ) == 0U );
209 
210             ret = ReadUnaligned( pInode, ullStart + ulReadIndex, ulRemaining, &pbBuffer[ ulReadIndex ] );
211         }
212 
213         if( ret == 0 )
214         {
215             *pulLen = ulLen;
216         }
217     }
218 
219     return ret;
220 }
221 
222 
223 #if REDCONF_READ_ONLY == 0
224 
225 /** @brief Write to an inode.
226  *
227  *  @param pInode   A pointer to the cached inode structure of the inode into
228  *                  which to write.
229  *  @param ullStart The file offset at which to write.
230  *  @param pulLen   On input, the number of bytes to attempt to write.  On
231  *                  successful return, populated with the number of bytes
232  *                  actually written.
233  *  @param pBuffer  The buffer to write from.
234  *
235  *  @return A negated ::REDSTATUS code indicating the operation result.
236  *
237  *  @retval 0           Operation was successful.
238  *  @retval -RED_EFBIG  @p ullStart is greater than the maximum file size; or
239  *                      @p ullStart is equal to the maximum file size and the
240  *                      write length is non-zero.
241  *  @retval -RED_EINVAL @p pInode is not a mounted cached inode pointer; or
242  *                      @p pulLen is `NULL`; or @p pBuffer is `NULL`.
243  *  @retval -RED_EIO    A disk I/O error occurred.
244  *  @retval -RED_ENOSPC No data can be written because there is insufficient
245  *                      free space.
246  */
RedInodeDataWrite(CINODE * pInode,uint64_t ullStart,uint32_t * pulLen,const void * pBuffer)247     REDSTATUS RedInodeDataWrite( CINODE * pInode,
248                                  uint64_t ullStart,
249                                  uint32_t * pulLen,
250                                  const void * pBuffer )
251     {
252         REDSTATUS ret = 0;
253 
254         if( !CINODE_IS_DIRTY( pInode ) || ( pulLen == NULL ) || ( pBuffer == NULL ) )
255         {
256             ret = -RED_EINVAL;
257         }
258         else if( ( ullStart > INODE_SIZE_MAX ) || ( ( ullStart == INODE_SIZE_MAX ) && ( *pulLen > 0U ) ) )
259         {
260             ret = -RED_EFBIG;
261         }
262         else if( *pulLen == 0U )
263         {
264             /*  Do nothing, just return success.
265              */
266         }
267         else
268         {
269             const uint8_t * pbBuffer = CAST_VOID_PTR_TO_CONST_UINT8_PTR( pBuffer );
270             uint32_t ulWriteIndex = 0U;
271             uint32_t ulLen = *pulLen;
272             uint32_t ulRemaining;
273 
274             if( ( INODE_SIZE_MAX - ullStart ) < ulLen )
275             {
276                 ulLen = ( uint32_t ) ( INODE_SIZE_MAX - ullStart );
277             }
278 
279             ulRemaining = ulLen;
280 
281             /*  If the write is beyond the current end of the file, and the current
282              *  end of the file is not block-aligned, then there may be some data
283              *  that needs to be zeroed in the last block.
284              */
285             if( ullStart > pInode->pInodeBuf->ullSize )
286             {
287                 ret = ExpandPrepare( pInode );
288             }
289 
290             /*  Partial block at start.
291              */
292             if( ( ret == 0 ) && ( ( ( ullStart & ( REDCONF_BLOCK_SIZE - 1U ) ) != 0U ) || ( ulRemaining < REDCONF_BLOCK_SIZE ) ) )
293             {
294                 uint32_t ulBytesInFirstBlock = REDCONF_BLOCK_SIZE - ( uint32_t ) ( ullStart & ( REDCONF_BLOCK_SIZE - 1U ) );
295                 uint32_t ulThisWrite = REDMIN( ulRemaining, ulBytesInFirstBlock );
296 
297                 ret = WriteUnaligned( pInode, ullStart, ulThisWrite, pbBuffer );
298 
299                 if( ret == 0 )
300                 {
301                     ulWriteIndex += ulThisWrite;
302                     ulRemaining -= ulThisWrite;
303                 }
304             }
305 
306             /*  Whole blocks.
307              */
308             if( ( ret == 0 ) && ( ulRemaining >= REDCONF_BLOCK_SIZE ) )
309             {
310                 uint32_t ulBlockOffset = ( uint32_t ) ( ( ullStart + ulWriteIndex ) >> BLOCK_SIZE_P2 );
311                 uint32_t ulBlockCount = ulRemaining >> BLOCK_SIZE_P2;
312                 uint32_t ulBlocksWritten = ulBlockCount;
313 
314                 REDASSERT( ( ( ullStart + ulWriteIndex ) & ( REDCONF_BLOCK_SIZE - 1U ) ) == 0U );
315 
316                 ret = WriteAligned( pInode, ulBlockOffset, &ulBlocksWritten, &pbBuffer[ ulWriteIndex ] );
317 
318                 if( ( ret == -RED_ENOSPC ) && ( ulWriteIndex > 0U ) )
319                 {
320                     ulBlocksWritten = 0U;
321                     ret = 0;
322                 }
323 
324                 if( ret == 0 )
325                 {
326                     ulWriteIndex += ulBlocksWritten << BLOCK_SIZE_P2;
327                     ulRemaining -= ulBlocksWritten << BLOCK_SIZE_P2;
328 
329                     if( ulBlocksWritten < ulBlockCount )
330                     {
331                         ulRemaining = 0U;
332                     }
333                 }
334             }
335 
336             /*  Partial block at end.
337              */
338             if( ( ret == 0 ) && ( ulRemaining > 0U ) )
339             {
340                 REDASSERT( ulRemaining < REDCONF_BLOCK_SIZE );
341                 REDASSERT( ( ( ullStart + ulWriteIndex ) & ( REDCONF_BLOCK_SIZE - 1U ) ) == 0U );
342                 REDASSERT( ulWriteIndex > 0U );
343 
344                 ret = WriteUnaligned( pInode, ullStart + ulWriteIndex, ulRemaining, &pbBuffer[ ulWriteIndex ] );
345 
346                 if( ret == -RED_ENOSPC )
347                 {
348                     ret = 0;
349                 }
350                 else if( ret == 0 )
351                 {
352                     ulWriteIndex += ulRemaining;
353 
354                     REDASSERT( ulWriteIndex == ulLen );
355                 }
356                 else
357                 {
358                     /*  Unexpected error, return it.
359                      */
360                 }
361             }
362 
363             if( ret == 0 )
364             {
365                 *pulLen = ulWriteIndex;
366 
367                 if( ( ullStart + ulWriteIndex ) > pInode->pInodeBuf->ullSize )
368                 {
369                     pInode->pInodeBuf->ullSize = ullStart + ulWriteIndex;
370                 }
371             }
372         }
373 
374         return ret;
375     }
376 
377 
378     #if DELETE_SUPPORTED || TRUNCATE_SUPPORTED
379 
380 /** @brief Change the size of an inode.
381  *
382  *  @param pInode   A pointer to the cached inode structure.
383  *  @param ullSize  The new file size for the inode.
384  *
385  *  @return A negated ::REDSTATUS code indicating the operation result.
386  *
387  *  @retval 0           Operation was successful.
388  *  @retval -RED_EFBIG  @p ullSize is greater than the maximum file size.
389  *  @retval -RED_EINVAL @p pInode is not a mounted cached inode pointer.
390  *  @retval -RED_EIO    A disk I/O error occurred.
391  *  @retval -RED_ENOSPC Insufficient free space to perform the truncate.
392  */
RedInodeDataTruncate(CINODE * pInode,uint64_t ullSize)393         REDSTATUS RedInodeDataTruncate( CINODE * pInode,
394                                         uint64_t ullSize )
395         {
396             REDSTATUS ret = 0;
397 
398             /*  The inode does not need to be dirtied when it is being deleted, because
399              *  the inode buffer will be discarded without ever being written to disk.
400              *  Thus, we only check to see if it's mounted here.
401              */
402             if( !CINODE_IS_MOUNTED( pInode ) )
403             {
404                 ret = -RED_EINVAL;
405             }
406             else if( ullSize > INODE_SIZE_MAX )
407             {
408                 ret = -RED_EFBIG;
409             }
410             else
411             {
412                 if( ullSize > pInode->pInodeBuf->ullSize )
413                 {
414                     ret = ExpandPrepare( pInode );
415                 }
416                 else if( ullSize < pInode->pInodeBuf->ullSize )
417                 {
418                     ret = Shrink( pInode, ullSize );
419                 }
420                 else
421                 {
422                     /*  Size is staying the same, nothing to do.
423                      */
424                 }
425 
426                 if( ret == 0 )
427                 {
428                     pInode->pInodeBuf->ullSize = ullSize;
429                 }
430             }
431 
432             return ret;
433         }
434 
435 
436 /** @brief Free all file data beyond a specified point.
437  *
438  *  @param pInode   A pointer to the cached inode structure.
439  *  @param ullSize  The point beyond which to free all file data.
440  *
441  *  @return A negated ::REDSTATUS code indicating the operation result.
442  *
443  *  @retval 0           Operation was successful.
444  *  @retval -RED_EIO    A disk I/O error occurred.
445  *  @retval -RED_ENOSPC Insufficient free space to perform the truncate.
446  *  @retval -RED_EINVAL Invalid parameters.
447  */
Shrink(CINODE * pInode,uint64_t ullSize)448         static REDSTATUS Shrink( CINODE * pInode,
449                                  uint64_t ullSize )
450         {
451             REDSTATUS ret = 0;
452 
453             /*  pInode->fDirty is checked explicitly here, instead of using the
454              *  CINODE_IS_DIRTY() macro, to avoid a duplicate mount check.
455              */
456             if( !CINODE_IS_MOUNTED( pInode ) || ( ( ullSize > 0U ) && !pInode->fDirty ) )
457             {
458                 REDERROR();
459                 ret = -RED_EINVAL;
460             }
461             else
462             {
463                 uint32_t ulTruncBlock = ( uint32_t ) ( ( ullSize + REDCONF_BLOCK_SIZE - 1U ) >> BLOCK_SIZE_P2 );
464 
465                 RedInodePutData( pInode );
466 
467                 #if REDCONF_DIRECT_POINTERS > 0U
468                     while( ulTruncBlock < REDCONF_DIRECT_POINTERS )
469                     {
470                         ret = TruncDataBlock( pInode, &pInode->pInodeBuf->aulEntries[ ulTruncBlock ], true );
471 
472                         if( ret != 0 )
473                         {
474                             break;
475                         }
476 
477                         ulTruncBlock++;
478                     }
479                 #endif /* if REDCONF_DIRECT_POINTERS > 0U */
480 
481                 #if REDCONF_INDIRECT_POINTERS > 0U
482                     while( ( ret == 0 ) && ( ulTruncBlock < ( REDCONF_DIRECT_POINTERS + INODE_INDIR_BLOCKS ) ) )
483                     {
484                         ret = RedInodeDataSeek( pInode, ulTruncBlock );
485 
486                         if( ( ret == 0 ) || ( ret == -RED_ENODATA ) )
487                         {
488                             bool fFreed;
489 
490                             ret = TruncIndir( pInode, &fFreed );
491 
492                             if( ret == 0 )
493                             {
494                                 if( fFreed )
495                                 {
496                                     pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ] = BLOCK_SPARSE;
497                                 }
498 
499                                 /*  The next seek will go to the beginning of the next
500                                  *  indirect.
501                                  */
502                                 ulTruncBlock += ( INDIR_ENTRIES - pInode->uIndirEntry );
503                             }
504                         }
505                     }
506                 #endif /* if REDCONF_INDIRECT_POINTERS > 0U */
507 
508                 #if DINDIR_POINTERS > 0U
509                     while( ( ret == 0 ) && ( ulTruncBlock < INODE_DATA_BLOCKS ) )
510                     {
511                         ret = RedInodeDataSeek( pInode, ulTruncBlock );
512 
513                         if( ( ret == 0 ) || ( ret == -RED_ENODATA ) )
514                         {
515                             bool fFreed;
516 
517                             /*  TruncDindir() invokes seek as it goes along, which will
518                              *  update the entry values (possibly all three of these);
519                              *  make a copy so we can compute things correctly after.
520                              */
521                             uint16_t uOrigInodeEntry = pInode->uInodeEntry;
522                             uint16_t uOrigDindirEntry = pInode->uDindirEntry;
523                             uint16_t uOrigIndirEntry = pInode->uIndirEntry;
524 
525                             ret = TruncDindir( pInode, &fFreed );
526 
527                             if( ret == 0 )
528                             {
529                                 if( fFreed )
530                                 {
531                                     pInode->pInodeBuf->aulEntries[ uOrigInodeEntry ] = BLOCK_SPARSE;
532                                 }
533 
534                                 /*  The next seek will go to the beginning of the next
535                                  *  double indirect.
536                                  */
537                                 ulTruncBlock += ( DINDIR_DATA_BLOCKS - ( uOrigDindirEntry * INDIR_ENTRIES ) ) - uOrigIndirEntry;
538                             }
539                         }
540                     }
541                 #endif /* if DINDIR_POINTERS > 0U */
542             }
543 
544             return ret;
545         }
546 
547 
548         #if DINDIR_POINTERS > 0U
549 
550 /** @brief Truncate a double indirect.
551  *
552  *  @param pInode   A pointer to the cached inode, whose coordinates indicate
553  *                  the truncation boundary.
554  *  @param pfFreed  On successful return, populated with whether the double
555  *                  indirect node was freed.
556  *
557  *  @return A negated ::REDSTATUS code indicating the operation result.
558  *
559  *  @retval 0           Operation was successful.
560  *  @retval -RED_EIO    A disk I/O error occurred.
561  *  @retval -RED_ENOSPC Insufficient free space to perform the truncate.
562  *  @retval -RED_EINVAL Invalid parameters.
563  */
TruncDindir(CINODE * pInode,bool * pfFreed)564             static REDSTATUS TruncDindir( CINODE * pInode,
565                                           bool * pfFreed )
566             {
567                 REDSTATUS ret = 0;
568 
569                 if( !CINODE_IS_MOUNTED( pInode ) || ( pfFreed == NULL ) )
570                 {
571                     REDERROR();
572                     ret = -RED_EINVAL;
573                 }
574                 else if( pInode->pDindir == NULL )
575                 {
576                     *pfFreed = false;
577                 }
578                 else
579                 {
580                     bool fBranch = false;
581                     uint16_t uEntry;
582 
583                     /*  The double indirect is definitely going to be branched (instead of
584                      *  deleted) if any of its indirect pointers which are entirely prior to
585                      *  the truncation boundary are non-sparse.
586                      */
587                     for( uEntry = 0U; !fBranch && ( uEntry < pInode->uDindirEntry ); uEntry++ )
588                     {
589                         fBranch = pInode->pDindir->aulEntries[ uEntry ] != BLOCK_SPARSE;
590                     }
591 
592                     /*  Unless we already know for a fact that the double indirect is going
593                      *  to be branched, examine the contents of the indirect pointer which
594                      *  straddles the truncation boundary.  If the indirect is going to be
595                      *  deleted, we know this indirect pointer is going away, and that might
596                      *  mean the double indirect is going to be deleted also.
597                      */
598                     if( !fBranch && ( pInode->pDindir->aulEntries[ pInode->uDindirEntry ] != BLOCK_SPARSE ) )
599                     {
600                         for( uEntry = 0U; !fBranch && ( uEntry < pInode->uIndirEntry ); uEntry++ )
601                         {
602                             fBranch = pInode->pIndir->aulEntries[ uEntry ] != BLOCK_SPARSE;
603                         }
604                     }
605 
606                     if( fBranch )
607                     {
608                         ret = BranchBlock( pInode, BRANCHDEPTH_DINDIR, false );
609                     }
610 
611                     if( ret == 0 )
612                     {
613                         uint32_t ulBlock = pInode->ulLogicalBlock;
614                         uint16_t uStart = pInode->uDindirEntry; /* pInode->uDindirEntry will change. */
615 
616                         for( uEntry = uStart; uEntry < INDIR_ENTRIES; uEntry++ )
617                         {
618                             /*  Seek so that TruncIndir() has the correct indirect
619                              *  buffer and indirect entry.
620                              */
621                             ret = RedInodeDataSeek( pInode, ulBlock );
622 
623                             if( ret == -RED_ENODATA )
624                             {
625                                 ret = 0;
626                             }
627 
628                             if( ( ret == 0 ) && ( pInode->ulIndirBlock != BLOCK_SPARSE ) )
629                             {
630                                 bool fIndirFreed;
631 
632                                 ret = TruncIndir( pInode, &fIndirFreed );
633 
634                                 if( ret == 0 )
635                                 {
636                                     /*  All of the indirects after the one which straddles
637                                      *  the truncation boundary should definitely end up
638                                      *  deleted.
639                                      */
640                                     REDASSERT( ( uEntry == uStart ) || fIndirFreed );
641 
642                                     /*  If the double indirect is being freed, all of the
643                                      *  indirects should be freed too.
644                                      */
645                                     REDASSERT( fIndirFreed || fBranch );
646 
647                                     if( fBranch && fIndirFreed )
648                                     {
649                                         pInode->pDindir->aulEntries[ uEntry ] = BLOCK_SPARSE;
650                                     }
651                                 }
652                             }
653 
654                             if( ret != 0 )
655                             {
656                                 break;
657                             }
658 
659                             ulBlock += ( INDIR_ENTRIES - pInode->uIndirEntry );
660                         }
661 
662                         if( ret == 0 )
663                         {
664                             *pfFreed = !fBranch;
665 
666                             if( !fBranch )
667                             {
668                                 RedInodePutDindir( pInode );
669 
670                                 ret = RedImapBlockSet( pInode->ulDindirBlock, false );
671                             }
672                         }
673                     }
674                 }
675 
676                 return ret;
677             }
678         #endif /* DINDIR_POINTERS > 0U */
679 
680 
681         #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
682 
683 /** @brief Truncate a indirect.
684  *
685  *  @param pInode   A pointer to the cached inode, whose coordinates indicate
686  *                  the truncation boundary.
687  *  @param pfFreed  On successful return, populated with whether the indirect
688  *                  node was freed.
689  *
690  *  @return A negated ::REDSTATUS code indicating the operation result.
691  *
692  *  @retval 0           Operation was successful.
693  *  @retval -RED_EIO    A disk I/O error occurred.
694  *  @retval -RED_ENOSPC Insufficient free space to perform the truncate.
695  *  @retval -RED_EINVAL Invalid parameters.
696  */
TruncIndir(CINODE * pInode,bool * pfFreed)697             static REDSTATUS TruncIndir( CINODE * pInode,
698                                          bool * pfFreed )
699             {
700                 REDSTATUS ret = 0;
701 
702                 if( !CINODE_IS_MOUNTED( pInode ) || ( pfFreed == NULL ) )
703                 {
704                     REDERROR();
705                     ret = -RED_EINVAL;
706                 }
707                 else if( pInode->pIndir == NULL )
708                 {
709                     *pfFreed = false;
710                 }
711                 else
712                 {
713                     bool fBranch = false;
714                     uint16_t uEntry;
715 
716                     /*  Scan the range of entries which are not being truncated.  If there
717                      *  is anything there, then the indirect will not be empty after the
718                      *  truncate, so it is branched and modified instead of deleted.
719                      */
720                     for( uEntry = 0U; !fBranch && ( uEntry < pInode->uIndirEntry ); uEntry++ )
721                     {
722                         fBranch = pInode->pIndir->aulEntries[ uEntry ] != BLOCK_SPARSE;
723                     }
724 
725                     if( fBranch )
726                     {
727                         ret = BranchBlock( pInode, BRANCHDEPTH_INDIR, false );
728                     }
729 
730                     if( ret == 0 )
731                     {
732                         for( uEntry = pInode->uIndirEntry; uEntry < INDIR_ENTRIES; uEntry++ )
733                         {
734                             ret = TruncDataBlock( pInode, &pInode->pIndir->aulEntries[ uEntry ], fBranch );
735 
736                             if( ret != 0 )
737                             {
738                                 break;
739                             }
740                         }
741 
742                         if( ret == 0 )
743                         {
744                             *pfFreed = !fBranch;
745 
746                             if( !fBranch )
747                             {
748                                 RedInodePutIndir( pInode );
749 
750                                 ret = RedImapBlockSet( pInode->ulIndirBlock, false );
751                             }
752                         }
753                     }
754                 }
755 
756                 return ret;
757             }
758         #endif /* REDCONF_DIRECT_POINTERS < INODE_ENTRIES */
759 
760 
761 /** @brief Truncate a file data block.
762  *
763  *  @param pInode       A pointer to the cached inode structure.
764  *  @param pulBlock     On entry, contains the block to be truncated.  On
765  *                      successful return, if @p fPropagate is true, populated
766  *                      with BLOCK_SPARSE, otherwise unmodified.
767  *  @param fPropagate   Whether the parent node is being branched.
768  *
769  *  @return A negated ::REDSTATUS code indicating the operation result.
770  *
771  *  @retval 0           Operation was successful.
772  *  @retval -RED_EIO    A disk I/O error occurred.
773  *  @retval -RED_EINVAL Invalid parameters.
774  */
TruncDataBlock(const CINODE * pInode,uint32_t * pulBlock,bool fPropagate)775         static REDSTATUS TruncDataBlock( const CINODE * pInode,
776                                          uint32_t * pulBlock,
777                                          bool fPropagate )
778         {
779             REDSTATUS ret = 0;
780 
781             if( !CINODE_IS_MOUNTED( pInode ) || ( pulBlock == NULL ) )
782             {
783                 REDERROR();
784                 ret = -RED_EINVAL;
785             }
786             else if( *pulBlock != BLOCK_SPARSE )
787             {
788                 ret = RedImapBlockSet( *pulBlock, false );
789 
790                 #if REDCONF_INODE_BLOCKS == 1
791                     if( ret == 0 )
792                     {
793                         if( pInode->pInodeBuf->ulBlocks == 0U )
794                         {
795                             CRITICAL_ERROR();
796                             ret = -RED_EFUBAR;
797                         }
798                         else
799                         {
800                             pInode->pInodeBuf->ulBlocks--;
801                         }
802                     }
803                 #endif /* if REDCONF_INODE_BLOCKS == 1 */
804 
805                 if( ( ret == 0 ) && fPropagate )
806                 {
807                     *pulBlock = BLOCK_SPARSE;
808                 }
809             }
810             else
811             {
812                 /*  Data block is sparse, nothing to truncate.
813                  */
814             }
815 
816             return ret;
817         }
818     #endif /* DELETE_SUPPORTED || TRUNCATE_SUPPORTED */
819 
820 
821 /** @brief Prepare to increase the file size.
822  *
823  *  When the inode size is increased, a sparse region is created.  It is
824  *  possible that a prior shrink operation to an unaligned size left stale data
825  *  beyond the end of the file in the last data block.  That data is not zeroed
826  *  while shrinking the inode in order to transfer the disk full burden from the
827  *  shrink operation to the expand operation.
828  *
829  *  @param pInode   A pointer to the cached inode structure.
830  *
831  *  @return A negated ::REDSTATUS code indicating the operation result.
832  *
833  *  @retval 0           Operation was successful.
834  *  @retval -RED_EIO    A disk I/O error occurred.
835  *  @retval -RED_ENOSPC Insufficient free space to perform the truncate.
836  *  @retval -RED_EINVAL Invalid parameters.
837  */
ExpandPrepare(CINODE * pInode)838     static REDSTATUS ExpandPrepare( CINODE * pInode )
839     {
840         REDSTATUS ret = 0;
841 
842         if( !CINODE_IS_DIRTY( pInode ) )
843         {
844             REDERROR();
845             ret = -RED_EINVAL;
846         }
847         else
848         {
849             uint32_t ulOldSizeByteInBlock = ( uint32_t ) ( pInode->pInodeBuf->ullSize & ( REDCONF_BLOCK_SIZE - 1U ) );
850 
851             if( ulOldSizeByteInBlock != 0U )
852             {
853                 ret = RedInodeDataSeek( pInode, ( uint32_t ) ( pInode->pInodeBuf->ullSize >> BLOCK_SIZE_P2 ) );
854 
855                 if( ret == -RED_ENODATA )
856                 {
857                     ret = 0;
858                 }
859                 else if( ret == 0 )
860                 {
861                     ret = BranchBlock( pInode, BRANCHDEPTH_FILE_DATA, true );
862 
863                     if( ret == 0 )
864                     {
865                         RedMemSet( &pInode->pbData[ ulOldSizeByteInBlock ], 0U, REDCONF_BLOCK_SIZE - ulOldSizeByteInBlock );
866                     }
867                 }
868                 else
869                 {
870                     REDERROR();
871                 }
872             }
873         }
874 
875         return ret;
876     }
877 #endif /* REDCONF_READ_ONLY == 0 */
878 
879 
880 /** @brief Seek to a given position within an inode, then buffer the data block.
881  *
882  *  On successful return, pInode->pbData will be populated with a buffer
883  *  corresponding to the @p ulBlock block offset.
884  *
885  *  @param pInode   A pointer to the cached inode structure.
886  *  @param ulBlock  The block offset to seek to and buffer.
887  *
888  *  @return A negated ::REDSTATUS code indicating the operation result.
889  *
890  *  @retval 0               Operation was successful.
891  *  @retval -RED_ENODATA    The block offset is sparse.
892  *  @retval -RED_EINVAL     @p ulBlock is too large.
893  *  @retval -RED_EIO        A disk I/O error occurred.
894  */
RedInodeDataSeekAndRead(CINODE * pInode,uint32_t ulBlock)895 REDSTATUS RedInodeDataSeekAndRead( CINODE * pInode,
896                                    uint32_t ulBlock )
897 {
898     REDSTATUS ret;
899 
900     ret = RedInodeDataSeek( pInode, ulBlock );
901 
902     if( ( ret == 0 ) && ( pInode->pbData == NULL ) )
903     {
904         REDASSERT( pInode->ulDataBlock != BLOCK_SPARSE );
905 
906         ret = RedBufferGet( pInode->ulDataBlock, 0U, CAST_VOID_PTR_PTR( &pInode->pbData ) );
907     }
908 
909     return ret;
910 }
911 
912 
913 /** @brief Seek to a given position within an inode.
914  *
915  *  On successful return, pInode->ulDataBlock will be populated with the
916  *  physical block number corresponding to the @p ulBlock block offset.
917  *
918  *  Note: Callers of this function depend on its parameter checking.
919  *
920  *  @param pInode   A pointer to the cached inode structure.
921  *  @param ulBlock  The block offset to seek to.
922  *
923  *  @return A negated ::REDSTATUS code indicating the operation result.
924  *
925  *  @retval 0               Operation was successful.
926  *  @retval -RED_ENODATA    The block offset is sparse.
927  *  @retval -RED_EINVAL     @p ulBlock is too large; or @p pInode is not a
928  *                          mounted cached inode pointer.
929  *  @retval -RED_EIO        A disk I/O error occurred.
930  */
RedInodeDataSeek(CINODE * pInode,uint32_t ulBlock)931 REDSTATUS RedInodeDataSeek( CINODE * pInode,
932                             uint32_t ulBlock )
933 {
934     REDSTATUS ret = 0;
935 
936     if( !CINODE_IS_MOUNTED( pInode ) || ( ulBlock >= INODE_DATA_BLOCKS ) )
937     {
938         ret = -RED_EINVAL;
939     }
940     else
941     {
942         SeekCoord( pInode, ulBlock );
943 
944         #if DINDIR_POINTERS > 0U
945             if( pInode->uDindirEntry != COORD_ENTRY_INVALID )
946             {
947                 if( pInode->ulDindirBlock == BLOCK_SPARSE )
948                 {
949                     /*  If the double indirect is unallocated, so is the indirect.
950                      */
951                     pInode->ulIndirBlock = BLOCK_SPARSE;
952                 }
953                 else
954                 {
955                     if( pInode->pDindir == NULL )
956                     {
957                         ret = RedBufferGet( pInode->ulDindirBlock, BFLAG_META_DINDIR, CAST_VOID_PTR_PTR( &pInode->pDindir ) );
958                     }
959 
960                     if( ret == 0 )
961                     {
962                         pInode->ulIndirBlock = pInode->pDindir->aulEntries[ pInode->uDindirEntry ];
963                     }
964                 }
965             }
966         #endif /* if DINDIR_POINTERS > 0U */
967 
968         #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
969             if( ( ret == 0 ) && ( pInode->uIndirEntry != COORD_ENTRY_INVALID ) )
970             {
971                 if( pInode->ulIndirBlock == BLOCK_SPARSE )
972                 {
973                     /*  If the indirect is unallocated, so is the data block.
974                      */
975                     pInode->ulDataBlock = BLOCK_SPARSE;
976                 }
977                 else
978                 {
979                     if( pInode->pIndir == NULL )
980                     {
981                         ret = RedBufferGet( pInode->ulIndirBlock, BFLAG_META_INDIR, CAST_VOID_PTR_PTR( &pInode->pIndir ) );
982                     }
983 
984                     if( ret == 0 )
985                     {
986                         pInode->ulDataBlock = pInode->pIndir->aulEntries[ pInode->uIndirEntry ];
987                     }
988                 }
989             }
990         #endif /* if REDCONF_DIRECT_POINTERS < INODE_ENTRIES */
991 
992         if( ( ret == 0 ) && ( pInode->ulDataBlock == BLOCK_SPARSE ) )
993         {
994             ret = -RED_ENODATA;
995         }
996     }
997 
998     return ret;
999 }
1000 
1001 
1002 /** @brief Seek to the coordinates.
1003  *
1004  *  Compute the new coordinates, and put any buffers which are not needed or are
1005  *  no longer appropriate.
1006  *
1007  *  @param pInode   A pointer to the cached inode structure.
1008  *  @param ulBlock  The block offset to seek to.
1009  */
SeekCoord(CINODE * pInode,uint32_t ulBlock)1010 static void SeekCoord( CINODE * pInode,
1011                        uint32_t ulBlock )
1012 {
1013     if( !CINODE_IS_MOUNTED( pInode ) || ( ulBlock >= INODE_DATA_BLOCKS ) )
1014     {
1015         REDERROR();
1016     }
1017     else if( ( pInode->ulLogicalBlock != ulBlock ) || !pInode->fCoordInited )
1018     {
1019         RedInodePutData( pInode );
1020         pInode->ulLogicalBlock = ulBlock;
1021 
1022         #if REDCONF_DIRECT_POINTERS > 0U
1023             #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
1024                 if( ulBlock < REDCONF_DIRECT_POINTERS )
1025             #endif
1026             {
1027                 #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
1028                     RedInodePutCoord( pInode );
1029                 #endif
1030 
1031                 pInode->uInodeEntry = ( uint16_t ) ulBlock;
1032                 pInode->ulDataBlock = pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ];
1033 
1034                 #if DINDIR_POINTERS > 0U
1035                     pInode->uDindirEntry = COORD_ENTRY_INVALID;
1036                 #endif
1037                 #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
1038                     pInode->uIndirEntry = COORD_ENTRY_INVALID;
1039                 #endif
1040             }
1041 
1042             #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
1043                 else
1044             #endif
1045         #endif /* if REDCONF_DIRECT_POINTERS > 0U */
1046         #if REDCONF_INDIRECT_POINTERS > 0U
1047             #if REDCONF_INDIRECT_POINTERS < INODE_ENTRIES
1048                 if( ulBlock < ( INODE_INDIR_BLOCKS + REDCONF_DIRECT_POINTERS ) )
1049             #endif
1050             {
1051                 uint32_t ulIndirRangeOffset = ulBlock - REDCONF_DIRECT_POINTERS;
1052                 uint16_t uInodeEntry = ( uint16_t ) ( ( ulIndirRangeOffset / INDIR_ENTRIES ) + REDCONF_DIRECT_POINTERS );
1053                 uint16_t uIndirEntry = ( uint16_t ) ( ulIndirRangeOffset % INDIR_ENTRIES );
1054 
1055                 #if DINDIR_POINTERS > 0U
1056                     RedInodePutDindir( pInode );
1057                 #endif
1058 
1059                 /*  If the inode entry is not changing, then the previous indirect
1060                  *  is still the correct one.  Otherwise, the old indirect will be
1061                  *  released and the new one will be read later.
1062                  */
1063                 if( ( pInode->uInodeEntry != uInodeEntry ) || !pInode->fCoordInited )
1064                 {
1065                     RedInodePutIndir( pInode );
1066 
1067                     pInode->uInodeEntry = uInodeEntry;
1068 
1069                     pInode->ulIndirBlock = pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ];
1070                 }
1071 
1072                 #if DINDIR_POINTERS > 0U
1073                     pInode->uDindirEntry = COORD_ENTRY_INVALID;
1074                 #endif
1075                 pInode->uIndirEntry = uIndirEntry;
1076 
1077                 /*  At this point, the following pInode members are needed but not
1078                  *  yet populated:
1079                  *
1080                  *  - pIndir
1081                  *  - ulDataBlock
1082                  */
1083             }
1084 
1085             #if DINDIR_POINTERS > 0U
1086                 else
1087             #endif
1088         #endif /* if REDCONF_INDIRECT_POINTERS > 0U */
1089         #if DINDIR_POINTERS > 0U
1090         {
1091             uint32_t ulDindirRangeOffset = ( ulBlock - REDCONF_DIRECT_POINTERS ) - INODE_INDIR_BLOCKS;
1092             uint16_t uInodeEntry = ( uint16_t ) ( ( ulDindirRangeOffset / DINDIR_DATA_BLOCKS ) + REDCONF_DIRECT_POINTERS + REDCONF_INDIRECT_POINTERS );
1093             uint32_t ulDindirNodeOffset = ulDindirRangeOffset % DINDIR_DATA_BLOCKS;
1094             uint16_t uDindirEntry = ( uint16_t ) ( ulDindirNodeOffset / INDIR_ENTRIES );
1095             uint16_t uIndirEntry = ( uint16_t ) ( ulDindirNodeOffset % INDIR_ENTRIES );
1096 
1097             /*  If the inode entry is not changing, then the previous double
1098              *  indirect is still the correct one.  Otherwise, the old double
1099              *  indirect will be released and the new one will be read later.
1100              */
1101             if( ( pInode->uInodeEntry != uInodeEntry ) || !pInode->fCoordInited )
1102             {
1103                 RedInodePutIndir( pInode );
1104                 RedInodePutDindir( pInode );
1105 
1106                 pInode->uInodeEntry = uInodeEntry;
1107 
1108                 pInode->ulDindirBlock = pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ];
1109             }
1110 
1111             /*  If neither the inode entry nor double indirect entry are
1112              *  changing, then the previous indirect is still the correct one.
1113              *  Otherwise, it old indirect will be released and the new one will
1114              *  be read later.
1115              */
1116             else if( pInode->uDindirEntry != uDindirEntry )
1117             {
1118                 RedInodePutIndir( pInode );
1119             }
1120             else
1121             {
1122                 /*  Data buffer has already been put, nothing to do.
1123                  */
1124             }
1125 
1126             pInode->uDindirEntry = uDindirEntry;
1127             pInode->uIndirEntry = uIndirEntry;
1128 
1129             /*  At this point, the following pInode members are needed but not
1130              *  yet populated:
1131              *
1132              *  - pDindir
1133              *  - pIndir
1134              *  - ulIndirBlock
1135              *  - ulDataBlock
1136              */
1137         }
1138         #elif ( REDCONF_DIRECT_POINTERS > 0U ) && ( REDCONF_INDIRECT_POINTERS > 0U )
1139             else
1140             {
1141                 /*  There are no double indirects, so the block should have been in
1142                  *  the direct or indirect range.
1143                  */
1144                 REDERROR();
1145             }
1146         #endif /* if DINDIR_POINTERS > 0U */
1147 
1148         pInode->fCoordInited = true;
1149     }
1150     else
1151     {
1152         /*  Seeking to the current position, nothing to do.
1153          */
1154     }
1155 }
1156 
1157 
1158 /** @brief Read an unaligned portion of a block.
1159  *
1160  *  @param pInode   A pointer to the cached inode structure.
1161  *  @param ullStart The file offset at which to read.
1162  *  @param ulLen    The number of bytes to read.
1163  *  @param pbBuffer The buffer to read into.
1164  *
1165  *  @return A negated ::REDSTATUS code indicating the operation result.
1166  *
1167  *  @retval 0           Operation was successful.
1168  *  @retval -RED_EIO    A disk I/O error occurred.
1169  *  @retval -RED_EINVAL Invalid parameters.
1170  */
ReadUnaligned(CINODE * pInode,uint64_t ullStart,uint32_t ulLen,uint8_t * pbBuffer)1171 static REDSTATUS ReadUnaligned( CINODE * pInode,
1172                                 uint64_t ullStart,
1173                                 uint32_t ulLen,
1174                                 uint8_t * pbBuffer )
1175 {
1176     REDSTATUS ret;
1177 
1178     /*  This read should not cross a block boundary.
1179      */
1180     if( ( ( ullStart >> BLOCK_SIZE_P2 ) != ( ( ( ullStart + ulLen ) - 1U ) >> BLOCK_SIZE_P2 ) ) ||
1181         ( pbBuffer == NULL ) )
1182     {
1183         REDERROR();
1184         ret = -RED_EINVAL;
1185     }
1186     else
1187     {
1188         ret = RedInodeDataSeekAndRead( pInode, ( uint32_t ) ( ullStart >> BLOCK_SIZE_P2 ) );
1189 
1190         if( ret == 0 )
1191         {
1192             RedMemCpy( pbBuffer, &pInode->pbData[ ullStart & ( REDCONF_BLOCK_SIZE - 1U ) ], ulLen );
1193         }
1194         else if( ret == -RED_ENODATA )
1195         {
1196             /*  Sparse block, return zeroed data.
1197              */
1198             RedMemSet( pbBuffer, 0U, ulLen );
1199             ret = 0;
1200         }
1201         else
1202         {
1203             /*  No action, just return the error.
1204              */
1205         }
1206     }
1207 
1208     return ret;
1209 }
1210 
1211 
1212 /** @brief Read one or more whole blocks.
1213  *
1214  *  @param pInode       A pointer to the cached inode structure.
1215  *  @param ulBlockStart The file block offset at which to read.
1216  *  @param ulBlockCount The number of blocks to read.
1217  *  @param pbBuffer     The buffer to read into.
1218  *
1219  *  @return A negated ::REDSTATUS code indicating the operation result.
1220  *
1221  *  @retval 0           Operation was successful.
1222  *  @retval -RED_EIO    A disk I/O error occurred.
1223  *  @retval -RED_EINVAL Invalid parameters.
1224  */
ReadAligned(CINODE * pInode,uint32_t ulBlockStart,uint32_t ulBlockCount,uint8_t * pbBuffer)1225 static REDSTATUS ReadAligned( CINODE * pInode,
1226                               uint32_t ulBlockStart,
1227                               uint32_t ulBlockCount,
1228                               uint8_t * pbBuffer )
1229 {
1230     REDSTATUS ret = 0;
1231 
1232     if( pbBuffer == NULL )
1233     {
1234         REDERROR();
1235         ret = -RED_EINVAL;
1236     }
1237     else
1238     {
1239         uint32_t ulBlockIndex = 0U;
1240 
1241         /*  Read the data from disk one contiguous extent at a time.
1242          */
1243         while( ( ret == 0 ) && ( ulBlockIndex < ulBlockCount ) )
1244         {
1245             uint32_t ulExtentStart;
1246             uint32_t ulExtentLen = ulBlockCount - ulBlockIndex;
1247 
1248             ret = GetExtent( pInode, ulBlockStart + ulBlockIndex, &ulExtentStart, &ulExtentLen );
1249 
1250             if( ret == 0 )
1251             {
1252                 #if REDCONF_READ_ONLY == 0
1253 
1254                     /*  Before reading directly from disk, flush any dirty file data
1255                      *  buffers in the range to avoid reading stale data.
1256                      */
1257                     ret = RedBufferFlush( ulExtentStart, ulExtentLen );
1258 
1259                     if( ret == 0 )
1260                 #endif
1261                 {
1262                     ret = RedIoRead( gbRedVolNum, ulExtentStart, ulExtentLen, &pbBuffer[ ulBlockIndex << BLOCK_SIZE_P2 ] );
1263 
1264                     if( ret == 0 )
1265                     {
1266                         ulBlockIndex += ulExtentLen;
1267                     }
1268                 }
1269             }
1270             else if( ret == -RED_ENODATA )
1271             {
1272                 /*  Sparse block, return zeroed data.
1273                  */
1274                 RedMemSet( &pbBuffer[ ulBlockIndex << BLOCK_SIZE_P2 ], 0U, REDCONF_BLOCK_SIZE );
1275                 ulBlockIndex++;
1276                 ret = 0;
1277             }
1278             else
1279             {
1280                 /*  An unexpected error occurred; the loop will terminate.
1281                  */
1282             }
1283         }
1284     }
1285 
1286     return ret;
1287 }
1288 
1289 
1290 #if REDCONF_READ_ONLY == 0
1291 
1292 /** @brief Write an unaligned portion of a block.
1293  *
1294  *  @param pInode   A pointer to the cached inode structure.
1295  *  @param ullStart The file offset at which to write.
1296  *  @param ulLen    The number of bytes to write.
1297  *  @param pbBuffer The buffer to write from.
1298  *
1299  *  @return A negated ::REDSTATUS code indicating the operation result.
1300  *
1301  *  @retval 0           Operation was successful.
1302  *  @retval -RED_EIO    A disk I/O error occurred.
1303  *  @retval -RED_ENOSPC No data can be written because there is insufficient
1304  *                      free space.
1305  *  @retval -RED_EINVAL Invalid parameters.
1306  */
WriteUnaligned(CINODE * pInode,uint64_t ullStart,uint32_t ulLen,const uint8_t * pbBuffer)1307     static REDSTATUS WriteUnaligned( CINODE * pInode,
1308                                      uint64_t ullStart,
1309                                      uint32_t ulLen,
1310                                      const uint8_t * pbBuffer )
1311     {
1312         REDSTATUS ret;
1313 
1314         /*  This write should not cross a block boundary.
1315          */
1316         if( ( ( ullStart >> BLOCK_SIZE_P2 ) != ( ( ( ullStart + ulLen ) - 1U ) >> BLOCK_SIZE_P2 ) ) ||
1317             ( pbBuffer == NULL ) )
1318         {
1319             REDERROR();
1320             ret = -RED_EINVAL;
1321         }
1322         else
1323         {
1324             ret = RedInodeDataSeek( pInode, ( uint32_t ) ( ullStart >> BLOCK_SIZE_P2 ) );
1325 
1326             if( ( ret == 0 ) || ( ret == -RED_ENODATA ) )
1327             {
1328                 ret = BranchBlock( pInode, BRANCHDEPTH_FILE_DATA, true );
1329 
1330                 if( ret == 0 )
1331                 {
1332                     RedMemCpy( &pInode->pbData[ ullStart & ( REDCONF_BLOCK_SIZE - 1U ) ], pbBuffer, ulLen );
1333                 }
1334             }
1335         }
1336 
1337         return ret;
1338     }
1339 
1340 
1341 /** @brief Write one or more whole blocks.
1342  *
1343  *  @param pInode           A pointer to the cached inode structure.
1344  *  @param ulBlockStart     The file block offset at which to write.
1345  *  @param pulBlockCount    On entry, the number of blocks to attempt to write.
1346  *                          On successful return, the number of blocks actually
1347  *                          written.
1348  *  @param pbBuffer         The buffer to write from.
1349  *
1350  *  @return A negated ::REDSTATUS code indicating the operation result.
1351  *
1352  *  @retval 0           Operation was successful.
1353  *  @retval -RED_EIO    A disk I/O error occurred.
1354  *  @retval -RED_ENOSPC No data can be written because there is insufficient
1355  *                      free space.
1356  *  @retval -RED_EINVAL Invalid parameters.
1357  */
WriteAligned(CINODE * pInode,uint32_t ulBlockStart,uint32_t * pulBlockCount,const uint8_t * pbBuffer)1358     static REDSTATUS WriteAligned( CINODE * pInode,
1359                                    uint32_t ulBlockStart,
1360                                    uint32_t * pulBlockCount,
1361                                    const uint8_t * pbBuffer )
1362     {
1363         REDSTATUS ret = 0;
1364 
1365         if( ( pulBlockCount == NULL ) || ( pbBuffer == NULL ) )
1366         {
1367             REDERROR();
1368             ret = -RED_EINVAL;
1369         }
1370         else
1371         {
1372             bool fFull = false;
1373             uint32_t ulBlockCount = *pulBlockCount;
1374             uint32_t ulBlockIndex;
1375 
1376             /*  Branch all of the file data blocks in advance.
1377              */
1378             for( ulBlockIndex = 0U; ( ulBlockIndex < ulBlockCount ) && !fFull; ulBlockIndex++ )
1379             {
1380                 ret = RedInodeDataSeek( pInode, ulBlockStart + ulBlockIndex );
1381 
1382                 if( ( ret == 0 ) || ( ret == -RED_ENODATA ) )
1383                 {
1384                     ret = BranchBlock( pInode, BRANCHDEPTH_FILE_DATA, false );
1385 
1386                     if( ret == -RED_ENOSPC )
1387                     {
1388                         if( ulBlockIndex > 0U )
1389                         {
1390                             ret = 0;
1391                         }
1392 
1393                         fFull = true;
1394                     }
1395                 }
1396 
1397                 if( ret != 0 )
1398                 {
1399                     break;
1400                 }
1401             }
1402 
1403             ulBlockCount = ulBlockIndex;
1404             ulBlockIndex = 0U;
1405 
1406             if( fFull )
1407             {
1408                 ulBlockCount--;
1409             }
1410 
1411             /*  Write the data to disk one contiguous extent at a time.
1412              */
1413             while( ( ret == 0 ) && ( ulBlockIndex < ulBlockCount ) )
1414             {
1415                 uint32_t ulExtentStart;
1416                 uint32_t ulExtentLen = ulBlockCount - ulBlockIndex;
1417 
1418                 ret = GetExtent( pInode, ulBlockStart + ulBlockIndex, &ulExtentStart, &ulExtentLen );
1419 
1420                 if( ret == 0 )
1421                 {
1422                     ret = RedIoWrite( gbRedVolNum, ulExtentStart, ulExtentLen, &pbBuffer[ ulBlockIndex << BLOCK_SIZE_P2 ] );
1423 
1424                     if( ret == 0 )
1425                     {
1426                         /*  If there is any buffered file data for the extent we
1427                          *  just wrote, those buffers are now stale.
1428                          */
1429                         ret = RedBufferDiscardRange( ulExtentStart, ulExtentLen );
1430                     }
1431 
1432                     if( ret == 0 )
1433                     {
1434                         ulBlockIndex += ulExtentLen;
1435                     }
1436                 }
1437             }
1438 
1439             if( ret == 0 )
1440             {
1441                 *pulBlockCount = ulBlockCount;
1442             }
1443         }
1444 
1445         return ret;
1446     }
1447 #endif /* REDCONF_READ_ONLY == 0 */
1448 
1449 
1450 /** @brief Get the physical block number and count of contiguous blocks given a
1451  *         starting logical block number.
1452  *
1453  *  @param pInode           A pointer to the cached inode structure.
1454  *  @param ulBlockStart     The file block offset for the start of the extent.
1455  *  @param pulExtentStart   On successful return, the starting physical block
1456  *                          number of the contiguous extent.
1457  *  @param pulExtentLen     On entry, the maximum length of the extent; on
1458  *                          successful return, the length of the contiguous
1459  *                          extent.
1460  *
1461  *  @return A negated ::REDSTATUS code indicating the operation result.
1462  *
1463  *  @retval 0               Operation was successful.
1464  *  @retval -RED_EIO        A disk I/O error occurred.
1465  *  @retval -RED_ENODATA    The block offset is sparse.
1466  *  @retval -RED_EINVAL     Invalid parameters.
1467  */
GetExtent(CINODE * pInode,uint32_t ulBlockStart,uint32_t * pulExtentStart,uint32_t * pulExtentLen)1468 static REDSTATUS GetExtent( CINODE * pInode,
1469                             uint32_t ulBlockStart,
1470                             uint32_t * pulExtentStart,
1471                             uint32_t * pulExtentLen )
1472 {
1473     REDSTATUS ret;
1474 
1475     if( ( pulExtentStart == NULL ) || ( pulExtentLen == NULL ) )
1476     {
1477         REDERROR();
1478         ret = -RED_EINVAL;
1479     }
1480     else
1481     {
1482         ret = RedInodeDataSeek( pInode, ulBlockStart );
1483 
1484         if( ret == 0 )
1485         {
1486             uint32_t ulExtentLen = *pulExtentLen;
1487             uint32_t ulFirstBlock = pInode->ulDataBlock;
1488             uint32_t ulRunLen = 1U;
1489 
1490             while( ( ret == 0 ) && ( ulRunLen < ulExtentLen ) )
1491             {
1492                 ret = RedInodeDataSeek( pInode, ulBlockStart + ulRunLen );
1493 
1494                 /*  The extent ends when we find a sparse data block or when the
1495                  *  data block is not contiguous with the preceding data block.
1496                  */
1497                 if( ( ret == -RED_ENODATA ) || ( ( ret == 0 ) && ( pInode->ulDataBlock != ( ulFirstBlock + ulRunLen ) ) ) )
1498                 {
1499                     ret = 0;
1500                     break;
1501                 }
1502 
1503                 ulRunLen++;
1504             }
1505 
1506             if( ret == 0 )
1507             {
1508                 *pulExtentStart = ulFirstBlock;
1509                 *pulExtentLen = ulRunLen;
1510             }
1511         }
1512     }
1513 
1514     return ret;
1515 }
1516 
1517 
1518 #if REDCONF_READ_ONLY == 0
1519 
1520 /** @brief Allocate or branch the file metadata path and data block if necessary.
1521  *
1522  *  Optionally, can stop allocating/branching at a certain depth.
1523  *
1524  *  @param pInode   A pointer to the cached inode structure.
1525  *  @param depth    A BRANCHDEPTH_ value indicating the lowest depth to branch.
1526  *  @param fBuffer  Whether to buffer the data block.
1527  *
1528  *  @return A negated ::REDSTATUS code indicating the operation result.
1529  *
1530  *  @retval 0           Operation was successful.
1531  *  @retval -RED_EIO    A disk I/O error occurred.
1532  *  @retval -RED_ENOSPC No data can be written because there is insufficient
1533  *                      free space.
1534  */
BranchBlock(CINODE * pInode,BRANCHDEPTH depth,bool fBuffer)1535     static REDSTATUS BranchBlock( CINODE * pInode,
1536                                   BRANCHDEPTH depth,
1537                                   bool fBuffer )
1538     {
1539         REDSTATUS ret;
1540         uint32_t ulCost = 0U; /* Init'd to quiet warnings. */
1541 
1542         ret = BranchBlockCost( pInode, depth, &ulCost );
1543 
1544         if( ( ret == 0 ) && ( ulCost > FreeBlockCount() ) )
1545         {
1546             ret = -RED_ENOSPC;
1547         }
1548 
1549         if( ret == 0 )
1550         {
1551             #if DINDIR_POINTERS > 0U
1552                 if( pInode->uDindirEntry != COORD_ENTRY_INVALID )
1553                 {
1554                     ret = BranchOneBlock( &pInode->ulDindirBlock, CAST_VOID_PTR_PTR( &pInode->pDindir ), BFLAG_META_DINDIR );
1555 
1556                     if( ret == 0 )
1557                     {
1558                         /*  In case we just created the double indirect.
1559                          */
1560                         pInode->pDindir->ulInode = pInode->ulInode;
1561 
1562                         pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ] = pInode->ulDindirBlock;
1563                     }
1564                 }
1565 
1566                 if( ret == 0 )
1567             #endif /* if DINDIR_POINTERS > 0U */
1568             #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
1569             {
1570                 if( ( pInode->uIndirEntry != COORD_ENTRY_INVALID ) && ( depth >= BRANCHDEPTH_INDIR ) )
1571                 {
1572                     ret = BranchOneBlock( &pInode->ulIndirBlock, CAST_VOID_PTR_PTR( &pInode->pIndir ), BFLAG_META_INDIR );
1573 
1574                     if( ret == 0 )
1575                     {
1576                         /*  In case we just created the indirect.
1577                          */
1578                         pInode->pIndir->ulInode = pInode->ulInode;
1579 
1580                         #if DINDIR_POINTERS > 0U
1581                             if( pInode->uDindirEntry != COORD_ENTRY_INVALID )
1582                             {
1583                                 pInode->pDindir->aulEntries[ pInode->uDindirEntry ] = pInode->ulIndirBlock;
1584                             }
1585                             else
1586                         #endif
1587                         {
1588                             pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ] = pInode->ulIndirBlock;
1589                         }
1590                     }
1591                 }
1592             }
1593 
1594             if( ret == 0 )
1595             #endif /* if REDCONF_DIRECT_POINTERS < INODE_ENTRIES */
1596             {
1597                 if( depth == BRANCHDEPTH_FILE_DATA )
1598                 {
1599                     #if REDCONF_INODE_BLOCKS == 1
1600                         bool fAllocedNew = ( pInode->ulDataBlock == BLOCK_SPARSE );
1601                     #endif
1602                     void ** ppBufPtr = ( fBuffer || ( pInode->pbData != NULL ) ) ? CAST_VOID_PTR_PTR( &pInode->pbData ) : NULL;
1603 
1604                     ret = BranchOneBlock( &pInode->ulDataBlock, ppBufPtr, 0U );
1605 
1606                     if( ret == 0 )
1607                     {
1608                         #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
1609                             if( pInode->uIndirEntry != COORD_ENTRY_INVALID )
1610                             {
1611                                 pInode->pIndir->aulEntries[ pInode->uIndirEntry ] = pInode->ulDataBlock;
1612                             }
1613                             else
1614                         #endif
1615                         {
1616                             pInode->pInodeBuf->aulEntries[ pInode->uInodeEntry ] = pInode->ulDataBlock;
1617                         }
1618 
1619                         #if REDCONF_INODE_BLOCKS == 1
1620                             if( fAllocedNew )
1621                             {
1622                                 if( pInode->pInodeBuf->ulBlocks < INODE_DATA_BLOCKS )
1623                                 {
1624                                     pInode->pInodeBuf->ulBlocks++;
1625                                 }
1626                                 else
1627                                 {
1628                                     CRITICAL_ERROR();
1629                                     ret = -RED_EFUBAR;
1630                                 }
1631                             }
1632                         #endif /* if REDCONF_INODE_BLOCKS == 1 */
1633                     }
1634                 }
1635             }
1636 
1637             CRITICAL_ASSERT( ret == 0 );
1638         }
1639 
1640         return ret;
1641     }
1642 
1643 
1644 /** @brief Branch a block.
1645  *
1646  *  The block can be a double indirect, indirect, or file data block.
1647  *
1648  *  The caller should have already handled the disk full implications of
1649  *  branching this block.
1650  *
1651  *  @param pulBlock On entry, the current block number, which may be
1652  *                  BLOCK_SPARSE if the block is to be newly allocated.  On
1653  *                  successful return, populated with the new block number,
1654  *                  which may be the same as the original block number if it
1655  *                  was not BLOCK_SPARSE and the block was already branched.
1656  *  @param ppBuffer If NULL, indicates that the caller does not want to buffer
1657  *                  the branched block.  If non-NULL, the caller does want the
1658  *                  branched block buffered, and the following is true:  On
1659  *                  entry, the current buffer for the block, if there is one, or
1660  *                  NULL if there is no buffer.  On successful exit, populated
1661  *                  with a buffer for the block, which will be dirty.  If the
1662  *                  block number is initially BLOCK_SPARSE, there should be no
1663  *                  buffer for the block.
1664  *  @param uBFlag   The buffer type flags: BFLAG_META_DINDIR, BFLAG_META_INDIR,
1665  *                  or zero for file data.
1666  *
1667  *  @retval 0           Operation was successful.
1668  *  @retval -RED_EIO    A disk I/O error occurred.
1669  *  @retval -RED_EINVAL Invalid parameters.
1670  */
BranchOneBlock(uint32_t * pulBlock,void ** ppBuffer,uint16_t uBFlag)1671     static REDSTATUS BranchOneBlock( uint32_t * pulBlock,
1672                                      void ** ppBuffer,
1673                                      uint16_t uBFlag )
1674     {
1675         REDSTATUS ret = 0;
1676 
1677         if( pulBlock == NULL )
1678         {
1679             REDERROR();
1680             ret = -RED_EINVAL;
1681         }
1682         else
1683         {
1684             ALLOCSTATE state = ALLOCSTATE_FREE;
1685             uint32_t ulPrevBlock = *pulBlock;
1686 
1687             if( ulPrevBlock != BLOCK_SPARSE )
1688             {
1689                 ret = RedImapBlockState( ulPrevBlock, &state );
1690             }
1691 
1692             if( ret == 0 )
1693             {
1694                 if( state == ALLOCSTATE_NEW )
1695                 {
1696                     /*  Block is already branched, so simply get it buffered dirty
1697                      *  if requested.
1698                      */
1699                     if( ppBuffer != NULL )
1700                     {
1701                         if( *ppBuffer != NULL )
1702                         {
1703                             RedBufferDirty( *ppBuffer );
1704                         }
1705                         else
1706                         {
1707                             ret = RedBufferGet( ulPrevBlock, uBFlag | BFLAG_DIRTY, ppBuffer );
1708                         }
1709                     }
1710                 }
1711                 else
1712                 {
1713                     /*  Block does not exist or is committed state, so allocate a
1714                      *  new block for the branch.
1715                      */
1716                     ret = RedImapAllocBlock( pulBlock );
1717 
1718                     if( ret == 0 )
1719                     {
1720                         if( ulPrevBlock == BLOCK_SPARSE )
1721                         {
1722                             /*  Block did not exist previously, so just get it
1723                              *  buffered if requested.
1724                              */
1725                             if( ppBuffer != NULL )
1726                             {
1727                                 if( *ppBuffer != NULL )
1728                                 {
1729                                     /*  How could there be an existing buffer when
1730                                      *  the block did not exist?
1731                                      */
1732                                     REDERROR();
1733                                     ret = -RED_EINVAL;
1734                                 }
1735                                 else
1736                                 {
1737                                     ret = RedBufferGet( *pulBlock, ( uint16_t ) ( ( uint32_t ) uBFlag | BFLAG_NEW | BFLAG_DIRTY ), ppBuffer );
1738                                 }
1739                             }
1740                         }
1741                         else
1742                         {
1743                             /*  Branch the buffer for the committed state block to
1744                              *  the newly allocated location.
1745                              */
1746                             if( ppBuffer != NULL )
1747                             {
1748                                 if( *ppBuffer == NULL )
1749                                 {
1750                                     ret = RedBufferGet( ulPrevBlock, uBFlag, ppBuffer );
1751                                 }
1752 
1753                                 if( ret == 0 )
1754                                 {
1755                                     RedBufferBranch( *ppBuffer, *pulBlock );
1756                                 }
1757                             }
1758 
1759                             /*  Mark the committed state block almost free.
1760                              */
1761                             if( ret == 0 )
1762                             {
1763                                 ret = RedImapBlockSet( ulPrevBlock, false );
1764                             }
1765                         }
1766                     }
1767                 }
1768             }
1769         }
1770 
1771         return ret;
1772     }
1773 
1774 
1775 /** @brief Compute the free space cost of branching a block.
1776  *
1777  *  The caller must first use RedInodeDataSeek() to the block to be branched.
1778  *
1779  *  @param pInode   A pointer to the cached inode structure, whose coordinates
1780  *                  indicate the block to be branched.
1781  *  @param depth    A BRANCHDEPTH_ value indicating how much of the file
1782  *                  metadata structure needs to be branched.
1783  *  @param pulCost  On successful return, populated with the number of blocks
1784  *                  that must be allocated from free space in order to branch
1785  *                  the given block.
1786  *
1787  *  @return A negated ::REDSTATUS code indicating the operation result.
1788  *
1789  *  @retval 0           Operation was successful.
1790  *  @retval -RED_EIO    A disk I/O error occurred.
1791  *  @retval -RED_EINVAL Invalid parameters.
1792  */
BranchBlockCost(const CINODE * pInode,BRANCHDEPTH depth,uint32_t * pulCost)1793     static REDSTATUS BranchBlockCost( const CINODE * pInode,
1794                                       BRANCHDEPTH depth,
1795                                       uint32_t * pulCost )
1796     {
1797         REDSTATUS ret = 0;
1798 
1799         if( !CINODE_IS_MOUNTED( pInode ) || !pInode->fCoordInited || ( depth > BRANCHDEPTH_MAX ) || ( pulCost == NULL ) )
1800         {
1801             REDERROR();
1802             ret = -RED_EINVAL;
1803         }
1804         else
1805         {
1806             ALLOCSTATE state;
1807 
1808             /*  ulCost is initialized to the maximum number of blocks that could
1809              *  be branched, and decremented for every block we determine does not
1810              *  need to be branched.
1811              */
1812             #if DINDIR_POINTERS > 0U
1813                 uint32_t ulCost = 3U;
1814             #elif REDCONF_DIRECT_POINTERS < INODE_ENTRIES
1815                 uint32_t ulCost = 2U;
1816             #else
1817                 uint32_t ulCost = 1U;
1818             #endif
1819 
1820             #if DINDIR_POINTERS > 0U
1821                 if( pInode->uDindirEntry != COORD_ENTRY_INVALID )
1822                 {
1823                     if( pInode->ulDindirBlock != BLOCK_SPARSE )
1824                     {
1825                         ret = RedImapBlockState( pInode->ulDindirBlock, &state );
1826 
1827                         if( ( ret == 0 ) && ( state == ALLOCSTATE_NEW ) )
1828                         {
1829                             /*  Double indirect already branched.
1830                              */
1831                             ulCost--;
1832                         }
1833                     }
1834                 }
1835                 else
1836                 {
1837                     /*  At this inode offset there are no double indirects.
1838                      */
1839                     ulCost--;
1840                 }
1841 
1842                 if( ret == 0 )
1843             #endif /* if DINDIR_POINTERS > 0U */
1844             #if REDCONF_DIRECT_POINTERS < INODE_ENTRIES
1845             {
1846                 if( ( pInode->uIndirEntry != COORD_ENTRY_INVALID ) && ( depth >= BRANCHDEPTH_INDIR ) )
1847                 {
1848                     if( pInode->ulIndirBlock != BLOCK_SPARSE )
1849                     {
1850                         ret = RedImapBlockState( pInode->ulIndirBlock, &state );
1851 
1852                         if( ( ret == 0 ) && ( state == ALLOCSTATE_NEW ) )
1853                         {
1854                             /*  Indirect already branched.
1855                              */
1856                             ulCost--;
1857                         }
1858                     }
1859                 }
1860                 else
1861                 {
1862                     /*  Either not branching this deep, or at this inode offset
1863                      *  there are no indirects.
1864                      */
1865                     ulCost--;
1866                 }
1867             }
1868 
1869             if( ret == 0 )
1870             #endif /* if REDCONF_DIRECT_POINTERS < INODE_ENTRIES */
1871             {
1872                 if( depth == BRANCHDEPTH_FILE_DATA )
1873                 {
1874                     if( pInode->ulDataBlock != BLOCK_SPARSE )
1875                     {
1876                         ret = RedImapBlockState( pInode->ulDataBlock, &state );
1877 
1878                         if( ( ret == 0 ) && ( state == ALLOCSTATE_NEW ) )
1879                         {
1880                             /*  File data block already branched.
1881                              */
1882                             ulCost--;
1883 
1884                             /*  If the file data block is branched, then its parent
1885                              *  nodes should be branched as well.
1886                              */
1887                             REDASSERT( ulCost == 0U );
1888                         }
1889                     }
1890                 }
1891                 else
1892                 {
1893                     /*  Not branching this deep.
1894                      */
1895                     ulCost--;
1896                 }
1897             }
1898 
1899             if( ret == 0 )
1900             {
1901                 *pulCost = ulCost;
1902             }
1903         }
1904 
1905         return ret;
1906     }
1907 
1908 
1909 /** @brief Yields the number of currently available free blocks.
1910  *
1911  *  Accounts for reserved blocks, subtracting the number of reserved blocks if
1912  *  they are unavailable.
1913  *
1914  *  @return Number of currently available free blocks.
1915  */
FreeBlockCount(void)1916     static uint32_t FreeBlockCount( void )
1917     {
1918         uint32_t ulFreeBlocks = gpRedMR->ulFreeBlocks;
1919 
1920         #if RESERVED_BLOCKS > 0U
1921             if( !gpRedCoreVol->fUseReservedBlocks )
1922             {
1923                 if( ulFreeBlocks >= RESERVED_BLOCKS )
1924                 {
1925                     ulFreeBlocks -= RESERVED_BLOCKS;
1926                 }
1927                 else
1928                 {
1929                     ulFreeBlocks = 0U;
1930                 }
1931             }
1932         #endif /* if RESERVED_BLOCKS > 0U */
1933 
1934         return ulFreeBlocks;
1935     }
1936 #endif /* REDCONF_READ_ONLY == 0 */
1937