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