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 directory operations.
29  */
30 #include <redfs.h>
31 
32 #if REDCONF_API_POSIX == 1
33 
34     #include <redcore.h>
35 
36 
37     #define DIR_INDEX_INVALID     UINT32_MAX
38 
39     #if ( REDCONF_NAME_MAX % 4U ) != 0U
40         #define DIRENT_PADDING    ( 4U - ( REDCONF_NAME_MAX % 4U ) )
41     #else
42         #define DIRENT_PADDING    ( 0U )
43     #endif
44     #define DIRENT_SIZE           ( 4U + REDCONF_NAME_MAX + DIRENT_PADDING )
45     #define DIRENTS_PER_BLOCK     ( REDCONF_BLOCK_SIZE / DIRENT_SIZE )
46     #define DIRENTS_MAX           ( uint32_t ) REDMIN( UINT32_MAX, UINT64_SUFFIX( 1 ) * INODE_DATA_BLOCKS * DIRENTS_PER_BLOCK )
47 
48 
49 /** @brief On-disk directory entry.
50  */
51     typedef struct
52     {
53         /** The inode number that the directory entry points at.  If the directory
54          *  entry is available, this holds INODE_INVALID.
55          */
56         uint32_t ulInode;
57 
58         /** The name of the directory entry.  For names shorter than
59          *  REDCONF_NAME_MAX, unused bytes in the array are zeroed.  For names of
60          *  the maximum length, the string is not null terminated.
61          */
62         char acName[ REDCONF_NAME_MAX ];
63 
64         #if DIRENT_PADDING > 0U
65 
66             /** Unused padding so that ulInode is always aligned on a four-byte
67              *  boundary.
68              */
69             uint8_t abPadding[ DIRENT_PADDING ];
70         #endif
71     } DIRENT;
72 
73 
74     #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_RENAME == 1 )
75         static REDSTATUS DirCyclicRenameCheck( uint32_t ulSrcInode,
76                                                const CINODE * pDstPInode );
77     #endif
78     #if REDCONF_READ_ONLY == 0
79         static REDSTATUS DirEntryWrite( CINODE * pPInode,
80                                         uint32_t ulIdx,
81                                         uint32_t ulInode,
82                                         const char * pszName,
83                                         uint32_t ulNameLen );
84         static uint64_t DirEntryIndexToOffset( uint32_t ulIdx );
85     #endif
86     static uint32_t DirOffsetToEntryIndex( uint64_t ullOffset );
87 
88 
89     #if REDCONF_READ_ONLY == 0
90 
91 /** @brief Create a new entry in a directory.
92  *
93  *  @param pPInode      A pointer to the cached inode structure of the directory
94  *                      to which the new entry will be added.
95  *  @param pszName      The name to be given to the new entry, terminated by a
96  *                      null or a path separator.
97  *  @param ulInode      The inode number the new name will point at.
98  *
99  *  @return A negated ::REDSTATUS code indicating the operation result.
100  *
101  *  @retval 0                   Operation was successful.
102  *  @retval -RED_EIO            A disk I/O error occurred.
103  *  @retval -RED_ENOSPC         There is not enough space on the volume to
104  *                              create the new directory entry; or the directory
105  *                              is full.
106  *  @retval -RED_ENOTDIR        @p pPInode is not a directory.
107  *  @retval -RED_ENAMETOOLONG   @p pszName is too long.
108  *  @retval -RED_EEXIST         @p pszName already exists in @p ulPInode.
109  *  @retval -RED_EINVAL         @p pPInode is not a mounted dirty cached inode
110  *                              structure; or @p pszName is not a valid name.
111  */
RedDirEntryCreate(CINODE * pPInode,const char * pszName,uint32_t ulInode)112         REDSTATUS RedDirEntryCreate( CINODE * pPInode,
113                                      const char * pszName,
114                                      uint32_t ulInode )
115         {
116             REDSTATUS ret;
117 
118             if( !CINODE_IS_DIRTY( pPInode ) || ( pszName == NULL ) || !INODE_IS_VALID( ulInode ) )
119             {
120                 ret = -RED_EINVAL;
121             }
122             else if( !pPInode->fDirectory )
123             {
124                 ret = -RED_ENOTDIR;
125             }
126             else
127             {
128                 uint32_t ulNameLen = RedNameLen( pszName );
129 
130                 if( ulNameLen == 0U )
131                 {
132                     ret = -RED_EINVAL;
133                 }
134                 else if( ulNameLen > REDCONF_NAME_MAX )
135                 {
136                     ret = -RED_ENAMETOOLONG;
137                 }
138                 else
139                 {
140                     uint32_t ulEntryIdx;
141 
142                     ret = RedDirEntryLookup( pPInode, pszName, &ulEntryIdx, NULL );
143 
144                     if( ret == 0 )
145                     {
146                         ret = -RED_EEXIST;
147                     }
148                     else if( ret == -RED_ENOENT )
149                     {
150                         if( ulEntryIdx == DIR_INDEX_INVALID )
151                         {
152                             ret = -RED_ENOSPC;
153                         }
154                         else
155                         {
156                             ret = 0;
157                         }
158                     }
159                     else
160                     {
161                         /*  Unexpected error, no action.
162                          */
163                     }
164 
165                     if( ret == 0 )
166                     {
167                         ret = DirEntryWrite( pPInode, ulEntryIdx, ulInode, pszName, ulNameLen );
168                     }
169                 }
170             }
171 
172             return ret;
173         }
174     #endif /* REDCONF_READ_ONLY == 0 */
175 
176 
177     #if DELETE_SUPPORTED
178 
179 /** @brief Delete an existing directory entry.
180  *
181  *  @param pPInode      A pointer to the cached inode structure of the directory
182  *                      containing the entry to be deleted.
183  *  @param ulDeleteIdx  Position within the directory of the entry to be
184  *                      deleted.
185  *
186  *  @return A negated ::REDSTATUS code indicating the operation result.
187  *
188  *  @retval 0               Operation was successful.
189  *  @retval -RED_EIO        A disk I/O error occurred.
190  *  @retval -RED_ENOSPC     The file system does not have enough space to modify
191  *                          the parent directory to perform the deletion.
192  *  @retval -RED_ENOTDIR    @p pPInode is not a directory.
193  *  @retval -RED_EINVAL     @p pPInode is not a mounted dirty cached inode
194  *                          structure; or @p ulIdx is out of range.
195  */
RedDirEntryDelete(CINODE * pPInode,uint32_t ulDeleteIdx)196         REDSTATUS RedDirEntryDelete( CINODE * pPInode,
197                                      uint32_t ulDeleteIdx )
198         {
199             REDSTATUS ret = 0;
200 
201             if( !CINODE_IS_DIRTY( pPInode ) || ( ulDeleteIdx >= DIRENTS_MAX ) )
202             {
203                 ret = -RED_EINVAL;
204             }
205             else if( !pPInode->fDirectory )
206             {
207                 ret = -RED_ENOTDIR;
208             }
209             else if( ( DirEntryIndexToOffset( ulDeleteIdx ) + DIRENT_SIZE ) == pPInode->pInodeBuf->ullSize )
210             {
211                 /*  Start searching one behind the index to be deleted.
212                  */
213                 uint32_t ulTruncIdx = ulDeleteIdx - 1U;
214                 bool fDone = false;
215 
216                 /*  We are deleting the last dirent in the directory, so search
217                  *  backwards to find the last populated dirent, allowing us to truncate
218                  *  the directory to that point.
219                  */
220                 while( ( ret == 0 ) && ( ulTruncIdx != UINT32_MAX ) && !fDone )
221                 {
222                     ret = RedInodeDataSeekAndRead( pPInode, ulTruncIdx / DIRENTS_PER_BLOCK );
223 
224                     if( ret == 0 )
225                     {
226                         const DIRENT * pDirents = CAST_CONST_DIRENT_PTR( pPInode->pbData );
227                         uint32_t ulBlockIdx = ulTruncIdx % DIRENTS_PER_BLOCK;
228 
229                         do
230                         {
231                             if( pDirents[ ulBlockIdx ].ulInode != INODE_INVALID )
232                             {
233                                 fDone = true;
234                                 break;
235                             }
236 
237                             ulTruncIdx--;
238                             ulBlockIdx--;
239                         } while( ulBlockIdx != UINT32_MAX );
240                     }
241                     else if( ret == -RED_ENODATA )
242                     {
243                         ret = 0;
244 
245                         REDASSERT( ( ulTruncIdx % DIRENTS_PER_BLOCK ) == 0U );
246                         ulTruncIdx -= DIRENTS_PER_BLOCK;
247                     }
248                     else
249                     {
250                         /*  Unexpected error, loop will terminate; nothing else
251                          *  to be done.
252                          */
253                     }
254                 }
255 
256                 /*  Currently ulTruncIdx represents the last valid dirent index, or
257                  *  UINT32_MAX if the directory is now empty.  Increment it so that it
258                  *  represents the first invalid entry, which will be truncated.
259                  */
260                 ulTruncIdx++;
261 
262                 /*  Truncate the directory, deleting the requested entry and any empty
263                  *  dirents at the end of the directory.
264                  */
265                 if( ret == 0 )
266                 {
267                     ret = RedInodeDataTruncate( pPInode, DirEntryIndexToOffset( ulTruncIdx ) );
268                 }
269             }
270             else
271             {
272                 /*  The dirent to delete is not the last entry in the directory, so just
273                  *  zero it.
274                  */
275                 ret = DirEntryWrite( pPInode, ulDeleteIdx, INODE_INVALID, "", 0U );
276             }
277 
278             return ret;
279         }
280     #endif /* DELETE_SUPPORTED */
281 
282 
283 /** @brief Perform a case-sensitive search of a directory for a given name.
284  *
285  *  If found, then position of the entry within the directory and the inode
286  *  number it points to are returned.  As an optimization for directory entry
287  *  creation, in the case where the requested entry does not exist, the position
288  *  of the first available (unused) entry is returned.
289  *
290  *  @param pPInode      A pointer to the cached inode structure of the directory
291  *                      to search.
292  *  @param pszName      The name of the desired entry, terminated by either a
293  *                      null or a path separator.
294  *  @param pulEntryIdx  On successful return, meaning that the desired entry
295  *                      exists, populated with the position of the entry.  If
296  *                      returning an -RED_ENOENT error, populated with the
297  *                      position of the first available entry, or set to
298  *                      DIR_INDEX_INVALID if the directory is full.  Optional.
299  *  @param pulInode     On successful return, populated with the inode number
300  *                      that the name points to.  Optional; may be `NULL`.
301  *
302  *  @return A negated ::REDSTATUS code indicating the operation result.
303  *
304  *  @retval 0                   Operation was successful.
305  *  @retval -RED_EIO            A disk I/O error occurred.
306  *  @retval -RED_ENOENT         @p pszName does not name an existing file or
307  *                              directory.
308  *  @retval -RED_ENOTDIR        @p pPInode is not a directory.
309  *  @retval -RED_EINVAL         @p pPInode is not a mounted cached inode
310  *                              structure; or @p pszName is not a valid name; or
311  *                              @p pulEntryIdx is `NULL`.
312  *  @retval -RED_ENAMETOOLONG   @p pszName is too long.
313  */
RedDirEntryLookup(CINODE * pPInode,const char * pszName,uint32_t * pulEntryIdx,uint32_t * pulInode)314     REDSTATUS RedDirEntryLookup( CINODE * pPInode,
315                                  const char * pszName,
316                                  uint32_t * pulEntryIdx,
317                                  uint32_t * pulInode )
318     {
319         REDSTATUS ret = 0;
320 
321         if( !CINODE_IS_MOUNTED( pPInode ) || ( pszName == NULL ) )
322         {
323             ret = -RED_EINVAL;
324         }
325         else if( !pPInode->fDirectory )
326         {
327             ret = -RED_ENOTDIR;
328         }
329         else
330         {
331             uint32_t ulNameLen = RedNameLen( pszName );
332 
333             if( ulNameLen == 0U )
334             {
335                 ret = -RED_EINVAL;
336             }
337             else if( ulNameLen > REDCONF_NAME_MAX )
338             {
339                 ret = -RED_ENAMETOOLONG;
340             }
341             else
342             {
343                 uint32_t ulIdx = 0U;
344                 uint32_t ulDirentCount = DirOffsetToEntryIndex( pPInode->pInodeBuf->ullSize );
345                 uint32_t ulFreeIdx = DIR_INDEX_INVALID; /* Index of first free dirent. */
346 
347                 /*  Loop over the directory blocks, searching each block for a
348                  *  dirent that matches the given name.
349                  */
350                 while( ( ret == 0 ) && ( ulIdx < ulDirentCount ) )
351                 {
352                     ret = RedInodeDataSeekAndRead( pPInode, ulIdx / DIRENTS_PER_BLOCK );
353 
354                     if( ret == 0 )
355                     {
356                         const DIRENT * pDirents = CAST_CONST_DIRENT_PTR( pPInode->pbData );
357                         uint32_t ulBlockLastIdx = REDMIN( DIRENTS_PER_BLOCK, ulDirentCount - ulIdx );
358                         uint32_t ulBlockIdx;
359 
360                         for( ulBlockIdx = 0U; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++ )
361                         {
362                             const DIRENT * pDirent = &pDirents[ ulBlockIdx ];
363 
364                             if( pDirent->ulInode != INODE_INVALID )
365                             {
366                                 /*  The name in the dirent will not be null
367                                  *  terminated if it is of the maximum length, so
368                                  *  use a bounded string compare and then make sure
369                                  *  there is nothing more to the name.
370                                  */
371                                 if( ( RedStrNCmp( pDirent->acName, pszName, ulNameLen ) == 0 ) &&
372                                     ( ( ulNameLen == REDCONF_NAME_MAX ) || ( pDirent->acName[ ulNameLen ] == '\0' ) ) )
373                                 {
374                                     /*  Found a matching dirent, stop and return its
375                                      *  information.
376                                      */
377                                     if( pulInode != NULL )
378                                     {
379                                         *pulInode = pDirent->ulInode;
380 
381                                         #ifdef REDCONF_ENDIAN_SWAP
382                                             *pulInode = RedRev32( *pulInode );
383                                         #endif
384                                     }
385 
386                                     ulIdx += ulBlockIdx;
387                                     break;
388                                 }
389                             }
390                             else if( ulFreeIdx == DIR_INDEX_INVALID )
391                             {
392                                 ulFreeIdx = ulIdx + ulBlockIdx;
393                             }
394                             else
395                             {
396                                 /*  The directory entry is free, but we already found a free one, so there's
397                                  *  nothing to do here.
398                                  */
399                             }
400                         }
401 
402                         if( ulBlockIdx < ulBlockLastIdx )
403                         {
404                             /*  If we broke out of the for loop, we found a matching
405                              *  dirent and can stop the search.
406                              */
407                             break;
408                         }
409 
410                         ulIdx += ulBlockLastIdx;
411                     }
412                     else if( ret == -RED_ENODATA )
413                     {
414                         if( ulFreeIdx == DIR_INDEX_INVALID )
415                         {
416                             ulFreeIdx = ulIdx;
417                         }
418 
419                         ret = 0;
420                         ulIdx += DIRENTS_PER_BLOCK;
421                     }
422                     else
423                     {
424                         /*  Unexpected error, let the loop terminate, no action
425                          *  here.
426                          */
427                     }
428                 }
429 
430                 if( ret == 0 )
431                 {
432                     /*  If we made it all the way to the end of the directory
433                      *  without stopping, then the given name does not exist in the
434                      *  directory.
435                      */
436                     if( ulIdx == ulDirentCount )
437                     {
438                         /*  If the directory had no sparse dirents, then the first
439                          *  free dirent is beyond the end of the directory.  If the
440                          *  directory is already the maximum size, then there is no
441                          *  free dirent.
442                          */
443                         if( ( ulFreeIdx == DIR_INDEX_INVALID ) && ( ulDirentCount < DIRENTS_MAX ) )
444                         {
445                             ulFreeIdx = ulDirentCount;
446                         }
447 
448                         ulIdx = ulFreeIdx;
449 
450                         ret = -RED_ENOENT;
451                     }
452 
453                     if( pulEntryIdx != NULL )
454                     {
455                         *pulEntryIdx = ulIdx;
456                     }
457                 }
458             }
459         }
460 
461         return ret;
462     }
463 
464 
465     #if ( REDCONF_API_POSIX_READDIR == 1 ) || ( REDCONF_CHECKER == 1 )
466 
467 /** @brief Read the next entry from a directory, given a starting index.
468  *
469  *  @param pPInode  A pointer to the cached inode structure of the directory to
470  *                  read from.
471  *  @param pulIdx   On entry, the directory index to start reading from.  On
472  *                  successful return, populated with the directory index to use
473  *                  for subsequent reads.  On -RED_ENOENT return, populated with
474  *                  the directory index immediately following the last valid
475  *                  one.
476  *  @param pszName  On successful return, populated with the name of the next
477  *                  directory entry.  Buffer must be at least
478  *                  REDCONF_NAME_MAX + 1 in size, to store the maximum name
479  *                  length plus a null terminator.
480  *  @param pulInode On successful return, populated with the inode number
481  *                  pointed at by the next directory entry.
482  *
483  *  @return A negated ::REDSTATUS code indicating the operation result.
484  *
485  *  @retval 0               Operation was successful.
486  *  @retval -RED_EIO        A disk I/O error occurred.
487  *  @retval -RED_ENOENT     There are no more entries in the directory.
488  *  @retval -RED_ENOTDIR    @p pPInode is not a directory.
489  *  @retval -RED_EINVAL     @p pPInode is not a mounted cached inode structure;
490  *                          or @p pszName is `NULL`; or @p pulIdx is `NULL`; or
491  *                          @p pulInode is `NULL`.
492  */
RedDirEntryRead(CINODE * pPInode,uint32_t * pulIdx,char * pszName,uint32_t * pulInode)493         REDSTATUS RedDirEntryRead( CINODE * pPInode,
494                                    uint32_t * pulIdx,
495                                    char * pszName,
496                                    uint32_t * pulInode )
497         {
498             REDSTATUS ret = 0;
499 
500             if( !CINODE_IS_MOUNTED( pPInode ) || ( pulIdx == NULL ) || ( pszName == NULL ) || ( pulInode == NULL ) )
501             {
502                 ret = -RED_EINVAL;
503             }
504             else if( !pPInode->fDirectory )
505             {
506                 ret = -RED_ENOTDIR;
507             }
508             else
509             {
510                 uint32_t ulIdx = *pulIdx;
511                 uint32_t ulDirentCount = DirOffsetToEntryIndex( pPInode->pInodeBuf->ullSize );
512 
513                 /*  Starting either at the beginning of the directory or where we left
514                  *  off, loop over the directory blocks, searching each block for a
515                  *  non-sparse dirent to return as the next entry in the directory.
516                  */
517                 while( ( ret == 0 ) && ( ulIdx < ulDirentCount ) )
518                 {
519                     uint32_t ulBlockOffset = ulIdx / DIRENTS_PER_BLOCK;
520 
521                     ret = RedInodeDataSeekAndRead( pPInode, ulBlockOffset );
522 
523                     if( ret == 0 )
524                     {
525                         const DIRENT * pDirents = CAST_CONST_DIRENT_PTR( pPInode->pbData );
526                         uint32_t ulBlockLastIdx = REDMIN( DIRENTS_PER_BLOCK, ulDirentCount - ( ulBlockOffset * DIRENTS_PER_BLOCK ) );
527                         uint32_t ulBlockIdx;
528 
529                         for( ulBlockIdx = ulIdx % DIRENTS_PER_BLOCK; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++ )
530                         {
531                             if( pDirents[ ulBlockIdx ].ulInode != INODE_INVALID )
532                             {
533                                 *pulIdx = ulIdx + 1U;
534                                 RedStrNCpy( pszName, pDirents[ ulBlockIdx ].acName, REDCONF_NAME_MAX );
535                                 pszName[ REDCONF_NAME_MAX ] = '\0';
536 
537                                 *pulInode = pDirents[ ulBlockIdx ].ulInode;
538 
539                                 #ifdef REDCONF_ENDIAN_SWAP
540                                     *pulInode = RedRev32( *pulInode );
541                                 #endif
542                                 break;
543                             }
544 
545                             ulIdx++;
546                         }
547 
548                         if( ulBlockIdx < ulBlockLastIdx )
549                         {
550                             REDASSERT( ulIdx <= ulDirentCount );
551                             break;
552                         }
553                     }
554                     else if( ret == -RED_ENODATA )
555                     {
556                         ulIdx += DIRENTS_PER_BLOCK - ( ulIdx % DIRENTS_PER_BLOCK );
557                         ret = 0;
558                     }
559                     else
560                     {
561                         /*  Unexpected error, loop will terminate; nothing else to do.
562                          */
563                     }
564 
565                     REDASSERT( ulIdx <= ulDirentCount );
566                 }
567 
568                 if( ( ret == 0 ) && ( ulIdx >= ulDirentCount ) )
569                 {
570                     *pulIdx = ulDirentCount;
571                     ret = -RED_ENOENT;
572                 }
573             }
574 
575             return ret;
576         }
577     #endif /* if ( REDCONF_API_POSIX_READDIR == 1 ) || ( REDCONF_CHECKER == 1 ) */
578 
579 
580     #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_RENAME == 1 )
581 
582 /** Rename a directory entry.
583  *
584  *  @param pSrcPInode   The inode of the directory containing @p pszSrcName.
585  *  @param pszSrcName   The name of the directory entry to be renamed.
586  *  @param pSrcInode    On successful return, populated with the inode of the
587  *                      source entry.
588  *  @param pDstPInode   The inode of the directory in which @p pszDstName will
589  *                      be created or replaced.
590  *  @param pszDstName   The name of the directory entry to be created or
591  *                      replaced.
592  *  @param pDstInode    On successful return, if the destination previously
593  *                      existed, populated with the inode previously pointed to
594  *                      by the destination.  This may be the same as the source
595  *                      inode.  If the destination did not exist, populated with
596  *                      INODE_INVALID.
597  *
598  *  @return A negated ::REDSTATUS code indicating the operation result.
599  *
600  *  @retval 0                   Operation was successful.
601  *  @retval -RED_EEXIST         #REDCONF_RENAME_ATOMIC is false and the
602  *                              destination name exists.
603  *  @retval -RED_EINVAL         @p pSrcPInode is not a mounted dirty cached
604  *                              inode structure; or @p pSrcInode is `NULL`; or
605  *                              @p pszSrcName is not a valid name; or
606  *                              @p pDstPInode is not a mounted dirty cached
607  *                              inode structure; or @p pDstInode is `NULL`; or
608  *                              @p pszDstName is not a valid name.
609  *  @retval -RED_EIO            A disk I/O error occurred.
610  *  @retval -RED_EISDIR         The destination name exists and is a directory,
611  *                              and the source name is a non-directory.
612  *  @retval -RED_ENAMETOOLONG   Either @p pszSrcName or @p pszDstName is longer
613  *                              than #REDCONF_NAME_MAX.
614  *  @retval -RED_ENOENT         The source name is not an existing entry; or
615  *                              either @p pszSrcName or @p pszDstName point to
616  *                              an empty string.
617  *  @retval -RED_ENOTDIR        @p pSrcPInode is not a directory; or
618  *                              @p pDstPInode is not a directory; or the source
619  *                              name is a directory and the destination name is
620  *                              a file.
621  *  @retval -RED_ENOTEMPTY      The destination name is a directory which is not
622  *                              empty.
623  *  @retval -RED_ENOSPC         The file system does not have enough space to
624  *                              extend the @p ulDstPInode directory.
625  *  @retval -RED_EROFS          The directory to be removed resides on a
626  *                              read-only file system.
627  */
RedDirEntryRename(CINODE * pSrcPInode,const char * pszSrcName,CINODE * pSrcInode,CINODE * pDstPInode,const char * pszDstName,CINODE * pDstInode)628         REDSTATUS RedDirEntryRename( CINODE * pSrcPInode,
629                                      const char * pszSrcName,
630                                      CINODE * pSrcInode,
631                                      CINODE * pDstPInode,
632                                      const char * pszDstName,
633                                      CINODE * pDstInode )
634         {
635             REDSTATUS ret;
636 
637             if( !CINODE_IS_DIRTY( pSrcPInode ) ||
638                 ( pszSrcName == NULL ) ||
639                 ( pSrcInode == NULL ) ||
640                 !CINODE_IS_DIRTY( pDstPInode ) ||
641                 ( pszDstName == NULL ) ||
642                 ( pDstInode == NULL ) )
643             {
644                 ret = -RED_EINVAL;
645             }
646             else if( !pSrcPInode->fDirectory || !pDstPInode->fDirectory )
647             {
648                 ret = -RED_ENOTDIR;
649             }
650             else
651             {
652                 uint32_t ulDstIdx = 0U; /* Init'd to quiet warnings. */
653                 uint32_t ulSrcIdx;
654 
655                 /*  Look up the source and destination names.
656                  */
657                 ret = RedDirEntryLookup( pSrcPInode, pszSrcName, &ulSrcIdx, &pSrcInode->ulInode );
658 
659                 if( ret == 0 )
660                 {
661                     ret = RedDirEntryLookup( pDstPInode, pszDstName, &ulDstIdx, &pDstInode->ulInode );
662 
663                     if( ret == -RED_ENOENT )
664                     {
665                         if( ulDstIdx == DIR_INDEX_INVALID )
666                         {
667                             ret = -RED_ENOSPC;
668                         }
669                         else
670                         {
671                             #if REDCONF_RENAME_ATOMIC == 1
672                                 pDstInode->ulInode = INODE_INVALID;
673                             #endif
674                             ret = 0;
675                         }
676                     }
677 
678                     #if REDCONF_RENAME_ATOMIC == 0
679                         else if( ret == 0 )
680                         {
681                             ret = -RED_EEXIST;
682                         }
683                         else
684                         {
685                             /*  Nothing to do here, just propagate the error.
686                              */
687                         }
688                     #endif
689                 }
690 
691                 #if REDCONF_RENAME_ATOMIC == 1
692 
693                     /*  If both names point to the same inode, POSIX says to do nothing to
694                      *  either name.
695                      */
696                     if( ( ret == 0 ) && ( pSrcInode->ulInode != pDstInode->ulInode ) )
697                 #else
698                     if( ret == 0 )
699                 #endif
700                 {
701                     ret = RedInodeMount( pSrcInode, FTYPE_EITHER, true );
702 
703                     #if REDCONF_RENAME_ATOMIC == 1
704                         if( ( ret == 0 ) && ( pDstInode->ulInode != INODE_INVALID ) )
705                         {
706                             /*  Source and destination must be the same type (file/dir).
707                              */
708                             ret = RedInodeMount( pDstInode, pSrcInode->fDirectory ? FTYPE_DIR : FTYPE_FILE, true );
709 
710                             /*  If renaming directories, the destination must be empty.
711                              */
712                             if( ( ret == 0 ) && pDstInode->fDirectory && ( pDstInode->pInodeBuf->ullSize > 0U ) )
713                             {
714                                 ret = -RED_ENOTEMPTY;
715                             }
716                         }
717                     #endif /* if REDCONF_RENAME_ATOMIC == 1 */
718 
719                     /*  If we are renaming a directory, make sure the rename isn't
720                      *  cyclic (e.g., renaming "foo" into "foo/bar").
721                      */
722                     if( ( ret == 0 ) && pSrcInode->fDirectory )
723                     {
724                         ret = DirCyclicRenameCheck( pSrcInode->ulInode, pDstPInode );
725                     }
726 
727                     if( ret == 0 )
728                     {
729                         ret = DirEntryWrite( pDstPInode, ulDstIdx, pSrcInode->ulInode, pszDstName, RedNameLen( pszDstName ) );
730                     }
731 
732                     if( ret == 0 )
733                     {
734                         ret = RedDirEntryDelete( pSrcPInode, ulSrcIdx );
735 
736                         if( ret == -RED_ENOSPC )
737                         {
738                             REDSTATUS ret2;
739 
740                             /*  If there was not enough space to branch the parent
741                              *  directory inode and data block containing the source
742                              *  entry, revert destination directory entry to its
743                              *  previous state.
744                              */
745                             #if REDCONF_RENAME_ATOMIC == 1
746                                 if( pDstInode->ulInode != INODE_INVALID )
747                                 {
748                                     ret2 = DirEntryWrite( pDstPInode, ulDstIdx, pDstInode->ulInode, pszDstName, RedNameLen( pszDstName ) );
749                                 }
750                                 else
751                             #endif
752                             {
753                                 ret2 = RedDirEntryDelete( pDstPInode, ulDstIdx );
754                             }
755 
756                             if( ret2 != 0 )
757                             {
758                                 ret = ret2;
759                                 CRITICAL_ERROR();
760                             }
761                         }
762                     }
763 
764                     if( ret == 0 )
765                     {
766                         pSrcInode->pInodeBuf->ulPInode = pDstPInode->ulInode;
767                     }
768                 }
769             }
770 
771             return ret;
772         }
773 
774 
775 /** @brief Check for a cyclic rename.
776  *
777  *  A cyclic rename is renaming a directory into a subdirectory of itself.  For
778  *  example, renaming "a" into "a/b/c/d" is cyclic.  These renames must not be
779  *  allowed since they would corrupt the directory tree.
780  *
781  *  @param ulSrcInode   The inode number of the directory being renamed.
782  *  @param pDstPInode   A pointer to the cached inode structure of the directory
783  *                      into which the source is being renamed.
784  *
785  *  @return A negated ::REDSTATUS code indicating the operation result.
786  *
787  *  @retval 0               Operation was successful.
788  *  @retval -RED_EIO        A disk I/O error occurred.
789  *  @retval -RED_EINVAL     The rename is cyclic; or invalid parameters.
790  *  @retval -RED_ENOTDIR    @p pDstPInode is not a directory.
791  */
DirCyclicRenameCheck(uint32_t ulSrcInode,const CINODE * pDstPInode)792         static REDSTATUS DirCyclicRenameCheck( uint32_t ulSrcInode,
793                                                const CINODE * pDstPInode )
794         {
795             REDSTATUS ret = 0;
796 
797             if( !INODE_IS_VALID( ulSrcInode ) || !CINODE_IS_MOUNTED( pDstPInode ) )
798             {
799                 REDERROR();
800                 ret = -RED_EINVAL;
801             }
802             else if( ulSrcInode == pDstPInode->ulInode )
803             {
804                 ret = -RED_EINVAL;
805             }
806             else if( !pDstPInode->fDirectory )
807             {
808                 ret = -RED_ENOTDIR;
809             }
810             else
811             {
812                 CINODE NextParent;
813 
814                 /*  Used to prevent infinite loop in case of corrupted directory
815                  *  structure.
816                  */
817                 uint32_t ulIteration = 0U;
818 
819                 NextParent.ulInode = pDstPInode->pInodeBuf->ulPInode;
820 
821                 while( ( NextParent.ulInode != ulSrcInode ) &&
822                        ( NextParent.ulInode != INODE_ROOTDIR ) &&
823                        ( NextParent.ulInode != INODE_INVALID ) &&
824                        ( ulIteration < gpRedVolConf->ulInodeCount ) )
825                 {
826                     ret = RedInodeMount( &NextParent, FTYPE_DIR, false );
827 
828                     if( ret != 0 )
829                     {
830                         break;
831                     }
832 
833                     NextParent.ulInode = NextParent.pInodeBuf->ulPInode;
834 
835                     RedInodePut( &NextParent, 0U );
836 
837                     ulIteration++;
838                 }
839 
840                 if( ( ret == 0 ) && ( ulIteration == gpRedVolConf->ulInodeCount ) )
841                 {
842                     CRITICAL_ERROR();
843                     ret = -RED_EFUBAR;
844                 }
845 
846                 if( ( ret == 0 ) && ( ulSrcInode == NextParent.ulInode ) )
847                 {
848                     ret = -RED_EINVAL;
849                 }
850             }
851 
852             return ret;
853         }
854     #endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1) */
855 
856 
857     #if REDCONF_READ_ONLY == 0
858 
859 /** @brief Update the contents of a directory entry.
860  *
861  *  @param pPInode      A pointer to the cached inode structure of the directory
862  *                      whose entry is being written.
863  *  @param ulIdx        The index of the directory entry to write.
864  *  @param ulInode      The inode number the directory entry is to point at.
865  *  @param pszName      The name of the directory entry.
866  *  @param ulNameLen    The length of @p pszName.
867  *
868  *  @return A negated ::REDSTATUS code indicating the operation result.
869  *
870  *  @retval 0               Operation was successful.
871  *  @retval -RED_EIO        A disk I/O error occurred.
872  *  @retval -RED_ENOSPC     There is not enough space on the volume to write the
873  *                          directory entry.
874  *  @retval -RED_ENOTDIR    @p pPInode is not a directory.
875  *  @retval -RED_EINVAL     Invalid parameters.
876  */
DirEntryWrite(CINODE * pPInode,uint32_t ulIdx,uint32_t ulInode,const char * pszName,uint32_t ulNameLen)877         static REDSTATUS DirEntryWrite( CINODE * pPInode,
878                                         uint32_t ulIdx,
879                                         uint32_t ulInode,
880                                         const char * pszName,
881                                         uint32_t ulNameLen )
882         {
883             REDSTATUS ret;
884 
885             if( !CINODE_IS_DIRTY( pPInode ) ||
886                 ( ulIdx >= DIRENTS_MAX ) ||
887                 ( !INODE_IS_VALID( ulInode ) && ( ulInode != INODE_INVALID ) ) ||
888                 ( pszName == NULL ) ||
889                 ( ulNameLen > REDCONF_NAME_MAX ) ||
890                 ( ( ulNameLen == 0U ) != ( ulInode == INODE_INVALID ) ) )
891             {
892                 REDERROR();
893                 ret = -RED_EINVAL;
894             }
895             else if( !pPInode->fDirectory )
896             {
897                 ret = -RED_ENOTDIR;
898             }
899             else
900             {
901                 uint64_t ullOffset = DirEntryIndexToOffset( ulIdx );
902                 uint32_t ulLen = DIRENT_SIZE;
903                 static DIRENT de;
904 
905                 RedMemSet( &de, 0U, sizeof( de ) );
906 
907                 de.ulInode = ulInode;
908 
909                 #ifdef REDCONF_ENDIAN_SWAP
910                     de.ulInode = RedRev32( de.ulInode );
911                 #endif
912 
913                 RedStrNCpy( de.acName, pszName, ulNameLen );
914 
915                 ret = RedInodeDataWrite( pPInode, ullOffset, &ulLen, &de );
916             }
917 
918             return ret;
919         }
920 
921 
922 /** @brief Convert a directory entry index to a byte offset.
923  *
924  *  @param ulIdx    Directory entry index.
925  *
926  *  @return Byte offset in the directory corresponding with ulIdx.
927  */
DirEntryIndexToOffset(uint32_t ulIdx)928         static uint64_t DirEntryIndexToOffset( uint32_t ulIdx )
929         {
930             uint32_t ulBlock = ulIdx / DIRENTS_PER_BLOCK;
931             uint32_t ulOffsetInBlock = ulIdx % DIRENTS_PER_BLOCK;
932             uint64_t ullOffset;
933 
934             REDASSERT( ulIdx < DIRENTS_MAX );
935 
936             ullOffset = ( uint64_t ) ulBlock << BLOCK_SIZE_P2;
937             ullOffset += ( uint64_t ) ulOffsetInBlock * DIRENT_SIZE;
938 
939             return ullOffset;
940         }
941     #endif /* REDCONF_READ_ONLY == 0 */
942 
943 
944 /** @brief Convert a byte offset to a directory entry index.
945  *
946  *  @param ullOffset    Byte offset in the directory.
947  *
948  *  @return Directory entry index corresponding with @p ullOffset.
949  */
DirOffsetToEntryIndex(uint64_t ullOffset)950     static uint32_t DirOffsetToEntryIndex( uint64_t ullOffset )
951     {
952         uint32_t ulIdx;
953 
954         REDASSERT( ullOffset < INODE_SIZE_MAX );
955         REDASSERT( ( ( uint32_t ) ( ullOffset & ( REDCONF_BLOCK_SIZE - 1U ) ) % DIRENT_SIZE ) == 0U );
956 
957         /*  Avoid doing any 64-bit divides.
958          */
959         ulIdx = ( uint32_t ) ( ullOffset >> BLOCK_SIZE_P2 ) * DIRENTS_PER_BLOCK;
960         ulIdx += ( uint32_t ) ( ullOffset & ( REDCONF_BLOCK_SIZE - 1U ) ) / DIRENT_SIZE;
961 
962         return ulIdx;
963     }
964 
965 
966 #endif /* REDCONF_API_POSIX == 1 */
967