| /* ----> DO NOT REMOVE THE FOLLOWING NOTICE <---- | |
| Copyright (c) 2014-2015 Datalight, Inc. | |
| All Rights Reserved Worldwide. | |
| This program is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; use version 2 of the License. | |
| This program is distributed in the hope that it will be useful, | |
| but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty | |
| of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License along | |
| with this program; if not, write to the Free Software Foundation, Inc., | |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
| */ | |
| /* Businesses and individuals that for commercial or other reasons cannot | |
| comply with the terms of the GPLv2 license may obtain a commercial license | |
| before incorporating Reliance Edge into proprietary software for | |
| distribution in any form. Visit http://www.datalight.com/reliance-edge for | |
| more information. | |
| */ | |
| /** @file | |
| @brief Implements directory operations. | |
| */ | |
| #include <redfs.h> | |
| #if REDCONF_API_POSIX == 1 | |
| #include <redcore.h> | |
| #define DIR_INDEX_INVALID UINT32_MAX | |
| #if (REDCONF_NAME_MAX % 4U) != 0U | |
| #define DIRENT_PADDING (4U - (REDCONF_NAME_MAX % 4U)) | |
| #else | |
| #define DIRENT_PADDING (0U) | |
| #endif | |
| #define DIRENT_SIZE (4U + REDCONF_NAME_MAX + DIRENT_PADDING) | |
| #define DIRENTS_PER_BLOCK (REDCONF_BLOCK_SIZE / DIRENT_SIZE) | |
| #define DIRENTS_MAX (uint32_t)REDMIN(UINT32_MAX, UINT64_SUFFIX(1) * INODE_DATA_BLOCKS * DIRENTS_PER_BLOCK) | |
| /** @brief On-disk directory entry. | |
| */ | |
| typedef struct | |
| { | |
| /** The inode number that the directory entry points at. If the directory | |
| entry is available, this holds INODE_INVALID. | |
| */ | |
| uint32_t ulInode; | |
| /** The name of the directory entry. For names shorter than | |
| REDCONF_NAME_MAX, unused bytes in the array are zeroed. For names of | |
| the maximum length, the string is not null terminated. | |
| */ | |
| char acName[REDCONF_NAME_MAX]; | |
| #if DIRENT_PADDING > 0U | |
| /** Unused padding so that ulInode is always aligned on a four-byte | |
| boundary. | |
| */ | |
| uint8_t abPadding[DIRENT_PADDING]; | |
| #endif | |
| } DIRENT; | |
| #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1) | |
| static REDSTATUS DirCyclicRenameCheck(uint32_t ulSrcInode, const CINODE *pDstPInode); | |
| #endif | |
| #if REDCONF_READ_ONLY == 0 | |
| static REDSTATUS DirEntryWrite(CINODE *pPInode, uint32_t ulIdx, uint32_t ulInode, const char *pszName, uint32_t ulNameLen); | |
| static uint64_t DirEntryIndexToOffset(uint32_t ulIdx); | |
| #endif | |
| static uint32_t DirOffsetToEntryIndex(uint64_t ullOffset); | |
| #if REDCONF_READ_ONLY == 0 | |
| /** @brief Create a new entry in a directory. | |
| @param pPInode A pointer to the cached inode structure of the directory | |
| to which the new entry will be added. | |
| @param pszName The name to be given to the new entry, terminated by a | |
| null or a path separator. | |
| @param ulInode The inode number the new name will point at. | |
| @return A negated ::REDSTATUS code indicating the operation result. | |
| @retval 0 Operation was successful. | |
| @retval -RED_EIO A disk I/O error occurred. | |
| @retval -RED_ENOSPC There is not enough space on the volume to | |
| create the new directory entry; or the directory | |
| is full. | |
| @retval -RED_ENOTDIR @p pPInode is not a directory. | |
| @retval -RED_ENAMETOOLONG @p pszName is too long. | |
| @retval -RED_EEXIST @p pszName already exists in @p ulPInode. | |
| @retval -RED_EINVAL @p pPInode is not a mounted dirty cached inode | |
| structure; or @p pszName is not a valid name. | |
| */ | |
| REDSTATUS RedDirEntryCreate( | |
| CINODE *pPInode, | |
| const char *pszName, | |
| uint32_t ulInode) | |
| { | |
| REDSTATUS ret; | |
| if(!CINODE_IS_DIRTY(pPInode) || (pszName == NULL) || !INODE_IS_VALID(ulInode)) | |
| { | |
| ret = -RED_EINVAL; | |
| } | |
| else if(!pPInode->fDirectory) | |
| { | |
| ret = -RED_ENOTDIR; | |
| } | |
| else | |
| { | |
| uint32_t ulNameLen = RedNameLen(pszName); | |
| if(ulNameLen == 0U) | |
| { | |
| ret = -RED_EINVAL; | |
| } | |
| else if(ulNameLen > REDCONF_NAME_MAX) | |
| { | |
| ret = -RED_ENAMETOOLONG; | |
| } | |
| else | |
| { | |
| uint32_t ulEntryIdx; | |
| ret = RedDirEntryLookup(pPInode, pszName, &ulEntryIdx, NULL); | |
| if(ret == 0) | |
| { | |
| ret = -RED_EEXIST; | |
| } | |
| else if(ret == -RED_ENOENT) | |
| { | |
| if(ulEntryIdx == DIR_INDEX_INVALID) | |
| { | |
| ret = -RED_ENOSPC; | |
| } | |
| else | |
| { | |
| ret = 0; | |
| } | |
| } | |
| else | |
| { | |
| /* Unexpected error, no action. | |
| */ | |
| } | |
| if(ret == 0) | |
| { | |
| ret = DirEntryWrite(pPInode, ulEntryIdx, ulInode, pszName, ulNameLen); | |
| } | |
| } | |
| } | |
| return ret; | |
| } | |
| #endif /* REDCONF_READ_ONLY == 0 */ | |
| #if DELETE_SUPPORTED | |
| /** @brief Delete an existing directory entry. | |
| @param pPInode A pointer to the cached inode structure of the directory | |
| containing the entry to be deleted. | |
| @param ulDeleteIdx Position within the directory of the entry to be | |
| deleted. | |
| @return A negated ::REDSTATUS code indicating the operation result. | |
| @retval 0 Operation was successful. | |
| @retval -RED_EIO A disk I/O error occurred. | |
| @retval -RED_ENOSPC The file system does not have enough space to modify | |
| the parent directory to perform the deletion. | |
| @retval -RED_ENOTDIR @p pPInode is not a directory. | |
| @retval -RED_EINVAL @p pPInode is not a mounted dirty cached inode | |
| structure; or @p ulIdx is out of range. | |
| */ | |
| REDSTATUS RedDirEntryDelete( | |
| CINODE *pPInode, | |
| uint32_t ulDeleteIdx) | |
| { | |
| REDSTATUS ret = 0; | |
| if(!CINODE_IS_DIRTY(pPInode) || (ulDeleteIdx >= DIRENTS_MAX)) | |
| { | |
| ret = -RED_EINVAL; | |
| } | |
| else if(!pPInode->fDirectory) | |
| { | |
| ret = -RED_ENOTDIR; | |
| } | |
| else if((DirEntryIndexToOffset(ulDeleteIdx) + DIRENT_SIZE) == pPInode->pInodeBuf->ullSize) | |
| { | |
| /* Start searching one behind the index to be deleted. | |
| */ | |
| uint32_t ulTruncIdx = ulDeleteIdx - 1U; | |
| bool fDone = false; | |
| /* We are deleting the last dirent in the directory, so search | |
| backwards to find the last populated dirent, allowing us to truncate | |
| the directory to that point. | |
| */ | |
| while((ret == 0) && (ulTruncIdx != UINT32_MAX) && !fDone) | |
| { | |
| ret = RedInodeDataSeekAndRead(pPInode, ulTruncIdx / DIRENTS_PER_BLOCK); | |
| if(ret == 0) | |
| { | |
| const DIRENT *pDirents = CAST_CONST_DIRENT_PTR(pPInode->pbData); | |
| uint32_t ulBlockIdx = ulTruncIdx % DIRENTS_PER_BLOCK; | |
| do | |
| { | |
| if(pDirents[ulBlockIdx].ulInode != INODE_INVALID) | |
| { | |
| fDone = true; | |
| break; | |
| } | |
| ulTruncIdx--; | |
| ulBlockIdx--; | |
| } while(ulBlockIdx != UINT32_MAX); | |
| } | |
| else if(ret == -RED_ENODATA) | |
| { | |
| ret = 0; | |
| REDASSERT((ulTruncIdx % DIRENTS_PER_BLOCK) == 0U); | |
| ulTruncIdx -= DIRENTS_PER_BLOCK; | |
| } | |
| else | |
| { | |
| /* Unexpected error, loop will terminate; nothing else | |
| to be done. | |
| */ | |
| } | |
| } | |
| /* Currently ulTruncIdx represents the last valid dirent index, or | |
| UINT32_MAX if the directory is now empty. Increment it so that it | |
| represents the first invalid entry, which will be truncated. | |
| */ | |
| ulTruncIdx++; | |
| /* Truncate the directory, deleting the requested entry and any empty | |
| dirents at the end of the directory. | |
| */ | |
| if(ret == 0) | |
| { | |
| ret = RedInodeDataTruncate(pPInode, DirEntryIndexToOffset(ulTruncIdx)); | |
| } | |
| } | |
| else | |
| { | |
| /* The dirent to delete is not the last entry in the directory, so just | |
| zero it. | |
| */ | |
| ret = DirEntryWrite(pPInode, ulDeleteIdx, INODE_INVALID, "", 0U); | |
| } | |
| return ret; | |
| } | |
| #endif /* DELETE_SUPPORTED */ | |
| /** @brief Perform a case-sensitive search of a directory for a given name. | |
| If found, then position of the entry within the directory and the inode | |
| number it points to are returned. As an optimization for directory entry | |
| creation, in the case where the requested entry does not exist, the position | |
| of the first available (unused) entry is returned. | |
| @param pPInode A pointer to the cached inode structure of the directory | |
| to search. | |
| @param pszName The name of the desired entry, terminated by either a | |
| null or a path separator. | |
| @param pulEntryIdx On successful return, meaning that the desired entry | |
| exists, populated with the position of the entry. If | |
| returning an -RED_ENOENT error, populated with the | |
| position of the first available entry, or set to | |
| DIR_INDEX_INVALID if the directory is full. Optional. | |
| @param pulInode On successful return, populated with the inode number | |
| that the name points to. Optional; may be `NULL`. | |
| @return A negated ::REDSTATUS code indicating the operation result. | |
| @retval 0 Operation was successful. | |
| @retval -RED_EIO A disk I/O error occurred. | |
| @retval -RED_ENOENT @p pszName does not name an existing file or | |
| directory. | |
| @retval -RED_ENOTDIR @p pPInode is not a directory. | |
| @retval -RED_EINVAL @p pPInode is not a mounted cached inode | |
| structure; or @p pszName is not a valid name; or | |
| @p pulEntryIdx is `NULL`. | |
| @retval -RED_ENAMETOOLONG @p pszName is too long. | |
| */ | |
| REDSTATUS RedDirEntryLookup( | |
| CINODE *pPInode, | |
| const char *pszName, | |
| uint32_t *pulEntryIdx, | |
| uint32_t *pulInode) | |
| { | |
| REDSTATUS ret = 0; | |
| if(!CINODE_IS_MOUNTED(pPInode) || (pszName == NULL)) | |
| { | |
| ret = -RED_EINVAL; | |
| } | |
| else if(!pPInode->fDirectory) | |
| { | |
| ret = -RED_ENOTDIR; | |
| } | |
| else | |
| { | |
| uint32_t ulNameLen = RedNameLen(pszName); | |
| if(ulNameLen == 0U) | |
| { | |
| ret = -RED_EINVAL; | |
| } | |
| else if(ulNameLen > REDCONF_NAME_MAX) | |
| { | |
| ret = -RED_ENAMETOOLONG; | |
| } | |
| else | |
| { | |
| uint32_t ulIdx = 0U; | |
| uint32_t ulDirentCount = DirOffsetToEntryIndex(pPInode->pInodeBuf->ullSize); | |
| uint32_t ulFreeIdx = DIR_INDEX_INVALID; /* Index of first free dirent. */ | |
| /* Loop over the directory blocks, searching each block for a | |
| dirent that matches the given name. | |
| */ | |
| while((ret == 0) && (ulIdx < ulDirentCount)) | |
| { | |
| ret = RedInodeDataSeekAndRead(pPInode, ulIdx / DIRENTS_PER_BLOCK); | |
| if(ret == 0) | |
| { | |
| const DIRENT *pDirents = CAST_CONST_DIRENT_PTR(pPInode->pbData); | |
| uint32_t ulBlockLastIdx = REDMIN(DIRENTS_PER_BLOCK, ulDirentCount - ulIdx); | |
| uint32_t ulBlockIdx; | |
| for(ulBlockIdx = 0U; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++) | |
| { | |
| const DIRENT *pDirent = &pDirents[ulBlockIdx]; | |
| if(pDirent->ulInode != INODE_INVALID) | |
| { | |
| /* The name in the dirent will not be null | |
| terminated if it is of the maximum length, so | |
| use a bounded string compare and then make sure | |
| there is nothing more to the name. | |
| */ | |
| if( (RedStrNCmp(pDirent->acName, pszName, ulNameLen) == 0) | |
| && ((ulNameLen == REDCONF_NAME_MAX) || (pDirent->acName[ulNameLen] == '\0'))) | |
| { | |
| /* Found a matching dirent, stop and return its | |
| information. | |
| */ | |
| if(pulInode != NULL) | |
| { | |
| *pulInode = pDirent->ulInode; | |
| #ifdef REDCONF_ENDIAN_SWAP | |
| *pulInode = RedRev32(*pulInode); | |
| #endif | |
| } | |
| ulIdx += ulBlockIdx; | |
| break; | |
| } | |
| } | |
| else if(ulFreeIdx == DIR_INDEX_INVALID) | |
| { | |
| ulFreeIdx = ulIdx + ulBlockIdx; | |
| } | |
| else | |
| { | |
| /* The directory entry is free, but we already found a free one, so there's | |
| nothing to do here. | |
| */ | |
| } | |
| } | |
| if(ulBlockIdx < ulBlockLastIdx) | |
| { | |
| /* If we broke out of the for loop, we found a matching | |
| dirent and can stop the search. | |
| */ | |
| break; | |
| } | |
| ulIdx += ulBlockLastIdx; | |
| } | |
| else if(ret == -RED_ENODATA) | |
| { | |
| if(ulFreeIdx == DIR_INDEX_INVALID) | |
| { | |
| ulFreeIdx = ulIdx; | |
| } | |
| ret = 0; | |
| ulIdx += DIRENTS_PER_BLOCK; | |
| } | |
| else | |
| { | |
| /* Unexpected error, let the loop terminate, no action | |
| here. | |
| */ | |
| } | |
| } | |
| if(ret == 0) | |
| { | |
| /* If we made it all the way to the end of the directory | |
| without stopping, then the given name does not exist in the | |
| directory. | |
| */ | |
| if(ulIdx == ulDirentCount) | |
| { | |
| /* If the directory had no sparse dirents, then the first | |
| free dirent is beyond the end of the directory. If the | |
| directory is already the maximum size, then there is no | |
| free dirent. | |
| */ | |
| if((ulFreeIdx == DIR_INDEX_INVALID) && (ulDirentCount < DIRENTS_MAX)) | |
| { | |
| ulFreeIdx = ulDirentCount; | |
| } | |
| ulIdx = ulFreeIdx; | |
| ret = -RED_ENOENT; | |
| } | |
| if(pulEntryIdx != NULL) | |
| { | |
| *pulEntryIdx = ulIdx; | |
| } | |
| } | |
| } | |
| } | |
| return ret; | |
| } | |
| #if (REDCONF_API_POSIX_READDIR == 1) || (REDCONF_CHECKER == 1) | |
| /** @brief Read the next entry from a directory, given a starting index. | |
| @param pPInode A pointer to the cached inode structure of the directory to | |
| read from. | |
| @param pulIdx On entry, the directory index to start reading from. On | |
| successful return, populated with the directory index to use | |
| for subsequent reads. On -RED_ENOENT return, populated with | |
| the directory index immediately following the last valid | |
| one. | |
| @param pszName On successful return, populated with the name of the next | |
| directory entry. Buffer must be at least | |
| REDCONF_NAME_MAX + 1 in size, to store the maximum name | |
| length plus a null terminator. | |
| @param pulInode On successful return, populated with the inode number | |
| pointed at by the next directory entry. | |
| @return A negated ::REDSTATUS code indicating the operation result. | |
| @retval 0 Operation was successful. | |
| @retval -RED_EIO A disk I/O error occurred. | |
| @retval -RED_ENOENT There are no more entries in the directory. | |
| @retval -RED_ENOTDIR @p pPInode is not a directory. | |
| @retval -RED_EINVAL @p pPInode is not a mounted cached inode structure; | |
| or @p pszName is `NULL`; or @p pulIdx is `NULL`; or | |
| @p pulInode is `NULL`. | |
| */ | |
| REDSTATUS RedDirEntryRead( | |
| CINODE *pPInode, | |
| uint32_t *pulIdx, | |
| char *pszName, | |
| uint32_t *pulInode) | |
| { | |
| REDSTATUS ret = 0; | |
| if(!CINODE_IS_MOUNTED(pPInode) || (pulIdx == NULL) || (pszName == NULL) || (pulInode == NULL)) | |
| { | |
| ret = -RED_EINVAL; | |
| } | |
| else if(!pPInode->fDirectory) | |
| { | |
| ret = -RED_ENOTDIR; | |
| } | |
| else | |
| { | |
| uint32_t ulIdx = *pulIdx; | |
| uint32_t ulDirentCount = DirOffsetToEntryIndex(pPInode->pInodeBuf->ullSize); | |
| /* Starting either at the beginning of the directory or where we left | |
| off, loop over the directory blocks, searching each block for a | |
| non-sparse dirent to return as the next entry in the directory. | |
| */ | |
| while((ret == 0) && (ulIdx < ulDirentCount)) | |
| { | |
| uint32_t ulBlockOffset = ulIdx / DIRENTS_PER_BLOCK; | |
| ret = RedInodeDataSeekAndRead(pPInode, ulBlockOffset); | |
| if(ret == 0) | |
| { | |
| const DIRENT *pDirents = CAST_CONST_DIRENT_PTR(pPInode->pbData); | |
| uint32_t ulBlockLastIdx = REDMIN(DIRENTS_PER_BLOCK, ulDirentCount - (ulBlockOffset * DIRENTS_PER_BLOCK)); | |
| uint32_t ulBlockIdx; | |
| for(ulBlockIdx = ulIdx % DIRENTS_PER_BLOCK; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++) | |
| { | |
| if(pDirents[ulBlockIdx].ulInode != INODE_INVALID) | |
| { | |
| *pulIdx = ulIdx + 1U; | |
| RedStrNCpy(pszName, pDirents[ulBlockIdx].acName, REDCONF_NAME_MAX); | |
| pszName[REDCONF_NAME_MAX] = '\0'; | |
| *pulInode = pDirents[ulBlockIdx].ulInode; | |
| #ifdef REDCONF_ENDIAN_SWAP | |
| *pulInode = RedRev32(*pulInode); | |
| #endif | |
| break; | |
| } | |
| ulIdx++; | |
| } | |
| if(ulBlockIdx < ulBlockLastIdx) | |
| { | |
| REDASSERT(ulIdx <= ulDirentCount); | |
| break; | |
| } | |
| } | |
| else if(ret == -RED_ENODATA) | |
| { | |
| ulIdx += DIRENTS_PER_BLOCK - (ulIdx % DIRENTS_PER_BLOCK); | |
| ret = 0; | |
| } | |
| else | |
| { | |
| /* Unexpected error, loop will terminate; nothing else to do. | |
| */ | |
| } | |
| REDASSERT(ulIdx <= ulDirentCount); | |
| } | |
| if((ret == 0) && (ulIdx >= ulDirentCount)) | |
| { | |
| *pulIdx = ulDirentCount; | |
| ret = -RED_ENOENT; | |
| } | |
| } | |
| return ret; | |
| } | |
| #endif | |
| #if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1) | |
| /** Rename a directory entry. | |
| @param pSrcPInode The inode of the directory containing @p pszSrcName. | |
| @param pszSrcName The name of the directory entry to be renamed. | |
| @param pSrcInode On successful return, populated with the inode of the | |
| source entry. | |
| @param pDstPInode The inode of the directory in which @p pszDstName will | |
| be created or replaced. | |
| @param pszDstName The name of the directory entry to be created or | |
| replaced. | |
| @param pDstInode On successful return, if the destination previously | |
| existed, populated with the inode previously pointed to | |
| by the destination. This may be the same as the source | |
| inode. If the destination did not exist, populated with | |
| INODE_INVALID. | |
| @return A negated ::REDSTATUS code indicating the operation result. | |
| @retval 0 Operation was successful. | |
| @retval -RED_EEXIST #REDCONF_RENAME_ATOMIC is false and the | |
| destination name exists. | |
| @retval -RED_EINVAL @p pSrcPInode is not a mounted dirty cached | |
| inode structure; or @p pSrcInode is `NULL`; or | |
| @p pszSrcName is not a valid name; or | |
| @p pDstPInode is not a mounted dirty cached | |
| inode structure; or @p pDstInode is `NULL`; or | |
| @p pszDstName is not a valid name. | |
| @retval -RED_EIO A disk I/O error occurred. | |
| @retval -RED_EISDIR The destination name exists and is a directory, | |
| and the source name is a non-directory. | |
| @retval -RED_ENAMETOOLONG Either @p pszSrcName or @p pszDstName is longer | |
| than #REDCONF_NAME_MAX. | |
| @retval -RED_ENOENT The source name is not an existing entry; or | |
| either @p pszSrcName or @p pszDstName point to | |
| an empty string. | |
| @retval -RED_ENOTDIR @p pSrcPInode is not a directory; or | |
| @p pDstPInode is not a directory; or the source | |
| name is a directory and the destination name is | |
| a file. | |
| @retval -RED_ENOTEMPTY The destination name is a directory which is not | |
| empty. | |
| @retval -RED_ENOSPC The file system does not have enough space to | |
| extend the @p ulDstPInode directory. | |
| @retval -RED_EROFS The directory to be removed resides on a | |
| read-only file system. | |
| */ | |
| REDSTATUS RedDirEntryRename( | |
| CINODE *pSrcPInode, | |
| const char *pszSrcName, | |
| CINODE *pSrcInode, | |
| CINODE *pDstPInode, | |
| const char *pszDstName, | |
| CINODE *pDstInode) | |
| { | |
| REDSTATUS ret; | |
| if( !CINODE_IS_DIRTY(pSrcPInode) | |
| || (pszSrcName == NULL) | |
| || (pSrcInode == NULL) | |
| || !CINODE_IS_DIRTY(pDstPInode) | |
| || (pszDstName == NULL) | |
| || (pDstInode == NULL)) | |
| { | |
| ret = -RED_EINVAL; | |
| } | |
| else if(!pSrcPInode->fDirectory || !pDstPInode->fDirectory) | |
| { | |
| ret = -RED_ENOTDIR; | |
| } | |
| else | |
| { | |
| uint32_t ulDstIdx = 0U; /* Init'd to quiet warnings. */ | |
| uint32_t ulSrcIdx; | |
| /* Look up the source and destination names. | |
| */ | |
| ret = RedDirEntryLookup(pSrcPInode, pszSrcName, &ulSrcIdx, &pSrcInode->ulInode); | |
| if(ret == 0) | |
| { | |
| ret = RedDirEntryLookup(pDstPInode, pszDstName, &ulDstIdx, &pDstInode->ulInode); | |
| if(ret == -RED_ENOENT) | |
| { | |
| if(ulDstIdx == DIR_INDEX_INVALID) | |
| { | |
| ret = -RED_ENOSPC; | |
| } | |
| else | |
| { | |
| #if REDCONF_RENAME_ATOMIC == 1 | |
| pDstInode->ulInode = INODE_INVALID; | |
| #endif | |
| ret = 0; | |
| } | |
| } | |
| #if REDCONF_RENAME_ATOMIC == 0 | |
| else if(ret == 0) | |
| { | |
| ret = -RED_EEXIST; | |
| } | |
| else | |
| { | |
| /* Nothing to do here, just propagate the error. | |
| */ | |
| } | |
| #endif | |
| } | |
| #if REDCONF_RENAME_ATOMIC == 1 | |
| /* If both names point to the same inode, POSIX says to do nothing to | |
| either name. | |
| */ | |
| if((ret == 0) && (pSrcInode->ulInode != pDstInode->ulInode)) | |
| #else | |
| if(ret == 0) | |
| #endif | |
| { | |
| ret = RedInodeMount(pSrcInode, FTYPE_EITHER, true); | |
| #if REDCONF_RENAME_ATOMIC == 1 | |
| if((ret == 0) && (pDstInode->ulInode != INODE_INVALID)) | |
| { | |
| /* Source and destination must be the same type (file/dir). | |
| */ | |
| ret = RedInodeMount(pDstInode, pSrcInode->fDirectory ? FTYPE_DIR : FTYPE_FILE, true); | |
| /* If renaming directories, the destination must be empty. | |
| */ | |
| if((ret == 0) && pDstInode->fDirectory && (pDstInode->pInodeBuf->ullSize > 0U)) | |
| { | |
| ret = -RED_ENOTEMPTY; | |
| } | |
| } | |
| #endif | |
| /* If we are renaming a directory, make sure the rename isn't | |
| cyclic (e.g., renaming "foo" into "foo/bar"). | |
| */ | |
| if((ret == 0) && pSrcInode->fDirectory) | |
| { | |
| ret = DirCyclicRenameCheck(pSrcInode->ulInode, pDstPInode); | |
| } | |
| if(ret == 0) | |
| { | |
| ret = DirEntryWrite(pDstPInode, ulDstIdx, pSrcInode->ulInode, pszDstName, RedNameLen(pszDstName)); | |
| } | |
| if(ret == 0) | |
| { | |
| ret = RedDirEntryDelete(pSrcPInode, ulSrcIdx); | |
| if(ret == -RED_ENOSPC) | |
| { | |
| REDSTATUS ret2; | |
| /* If there was not enough space to branch the parent | |
| directory inode and data block containin the source | |
| entry, revert destination directory entry to its | |
| previous state. | |
| */ | |
| #if REDCONF_RENAME_ATOMIC == 1 | |
| if(pDstInode->ulInode != INODE_INVALID) | |
| { | |
| ret2 = DirEntryWrite(pDstPInode, ulDstIdx, pDstInode->ulInode, pszDstName, RedNameLen(pszDstName)); | |
| } | |
| else | |
| #endif | |
| { | |
| ret2 = RedDirEntryDelete(pDstPInode, ulDstIdx); | |
| } | |
| if(ret2 != 0) | |
| { | |
| ret = ret2; | |
| CRITICAL_ERROR(); | |
| } | |
| } | |
| } | |
| if(ret == 0) | |
| { | |
| pSrcInode->pInodeBuf->ulPInode = pDstPInode->ulInode; | |
| } | |
| } | |
| } | |
| return ret; | |
| } | |
| /** @brief Check for a cyclic rename. | |
| A cyclic rename is renaming a directory into a subdirectory of itself. For | |
| example, renaming "a" into "a/b/c/d" is cyclic. These renames must not be | |
| allowed since they would corrupt the directory tree. | |
| @param ulSrcInode The inode number of the directory being renamed. | |
| @param pDstPInode A pointer to the cached inode structure of the directory | |
| into which the source is being renamed. | |
| @return A negated ::REDSTATUS code indicating the operation result. | |
| @retval 0 Operation was successful. | |
| @retval -RED_EIO A disk I/O error occurred. | |
| @retval -RED_EINVAL The rename is cyclic; or invalid parameters. | |
| @retval -RED_ENOTDIR @p pDstPInode is not a directory. | |
| */ | |
| static REDSTATUS DirCyclicRenameCheck( | |
| uint32_t ulSrcInode, | |
| const CINODE *pDstPInode) | |
| { | |
| REDSTATUS ret = 0; | |
| if(!INODE_IS_VALID(ulSrcInode) || !CINODE_IS_MOUNTED(pDstPInode)) | |
| { | |
| REDERROR(); | |
| ret = -RED_EINVAL; | |
| } | |
| else if(ulSrcInode == pDstPInode->ulInode) | |
| { | |
| ret = -RED_EINVAL; | |
| } | |
| else if(!pDstPInode->fDirectory) | |
| { | |
| ret = -RED_ENOTDIR; | |
| } | |
| else | |
| { | |
| CINODE NextParent; | |
| /* Used to prevent infinite loop in case of corrupted directory | |
| structure. | |
| */ | |
| uint32_t ulIteration = 0U; | |
| NextParent.ulInode = pDstPInode->pInodeBuf->ulPInode; | |
| while( (NextParent.ulInode != ulSrcInode) | |
| && (NextParent.ulInode != INODE_ROOTDIR) | |
| && (NextParent.ulInode != INODE_INVALID) | |
| && (ulIteration < gpRedVolConf->ulInodeCount)) | |
| { | |
| ret = RedInodeMount(&NextParent, FTYPE_DIR, false); | |
| if(ret != 0) | |
| { | |
| break; | |
| } | |
| NextParent.ulInode = NextParent.pInodeBuf->ulPInode; | |
| RedInodePut(&NextParent, 0U); | |
| ulIteration++; | |
| } | |
| if((ret == 0) && (ulIteration == gpRedVolConf->ulInodeCount)) | |
| { | |
| CRITICAL_ERROR(); | |
| ret = -RED_EFUBAR; | |
| } | |
| if((ret == 0) && (ulSrcInode == NextParent.ulInode)) | |
| { | |
| ret = -RED_EINVAL; | |
| } | |
| } | |
| return ret; | |
| } | |
| #endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1) */ | |
| #if REDCONF_READ_ONLY == 0 | |
| /** @brief Update the contents of a directory entry. | |
| @param pPInode A pointer to the cached inode structure of the directory | |
| whose entry is being written. | |
| @param ulIdx The index of the directory entry to write. | |
| @param ulInode The inode number the directory entry is to point at. | |
| @param pszName The name of the directory entry. | |
| @param ulNameLen The length of @p pszName. | |
| @return A negated ::REDSTATUS code indicating the operation result. | |
| @retval 0 Operation was successful. | |
| @retval -RED_EIO A disk I/O error occurred. | |
| @retval -RED_ENOSPC There is not enough space on the volume to write the | |
| directory entry. | |
| @retval -RED_ENOTDIR @p pPInode is not a directory. | |
| @retval -RED_EINVAL Invalid parameters. | |
| */ | |
| static REDSTATUS DirEntryWrite( | |
| CINODE *pPInode, | |
| uint32_t ulIdx, | |
| uint32_t ulInode, | |
| const char *pszName, | |
| uint32_t ulNameLen) | |
| { | |
| REDSTATUS ret; | |
| if( !CINODE_IS_DIRTY(pPInode) | |
| || (ulIdx >= DIRENTS_MAX) | |
| || (!INODE_IS_VALID(ulInode) && (ulInode != INODE_INVALID)) | |
| || (pszName == NULL) | |
| || (ulNameLen > REDCONF_NAME_MAX) | |
| || ((ulNameLen == 0U) != (ulInode == INODE_INVALID))) | |
| { | |
| REDERROR(); | |
| ret = -RED_EINVAL; | |
| } | |
| else if(!pPInode->fDirectory) | |
| { | |
| ret = -RED_ENOTDIR; | |
| } | |
| else | |
| { | |
| uint64_t ullOffset = DirEntryIndexToOffset(ulIdx); | |
| uint32_t ulLen = DIRENT_SIZE; | |
| static DIRENT de; | |
| RedMemSet(&de, 0U, sizeof(de)); | |
| de.ulInode = ulInode; | |
| #ifdef REDCONF_ENDIAN_SWAP | |
| de.ulInode = RedRev32(de.ulInode); | |
| #endif | |
| RedStrNCpy(de.acName, pszName, ulNameLen); | |
| ret = RedInodeDataWrite(pPInode, ullOffset, &ulLen, &de); | |
| } | |
| return ret; | |
| } | |
| /** @brief Convert a directory entry index to a byte offset. | |
| @param ulIdx Directory entry index. | |
| @return Byte offset in the directory corresponding with ulIdx. | |
| */ | |
| static uint64_t DirEntryIndexToOffset( | |
| uint32_t ulIdx) | |
| { | |
| uint32_t ulBlock = ulIdx / DIRENTS_PER_BLOCK; | |
| uint32_t ulOffsetInBlock = ulIdx % DIRENTS_PER_BLOCK; | |
| uint64_t ullOffset; | |
| REDASSERT(ulIdx < DIRENTS_MAX); | |
| ullOffset = (uint64_t)ulBlock << BLOCK_SIZE_P2; | |
| ullOffset += (uint64_t)ulOffsetInBlock * DIRENT_SIZE; | |
| return ullOffset; | |
| } | |
| #endif /* REDCONF_READ_ONLY == 0 */ | |
| /** @brief Convert a byte offset to a directory entry index. | |
| @param ullOffset Byte offset in the directory. | |
| @return Directory entry index corresponding with @p ullOffset. | |
| */ | |
| static uint32_t DirOffsetToEntryIndex( | |
| uint64_t ullOffset) | |
| { | |
| uint32_t ulIdx; | |
| REDASSERT(ullOffset < INODE_SIZE_MAX); | |
| REDASSERT(((uint32_t)(ullOffset & (REDCONF_BLOCK_SIZE - 1U)) % DIRENT_SIZE) == 0U); | |
| /* Avoid doing any 64-bit divides. | |
| */ | |
| ulIdx = (uint32_t)(ullOffset >> BLOCK_SIZE_P2) * DIRENTS_PER_BLOCK; | |
| ulIdx += (uint32_t)(ullOffset & (REDCONF_BLOCK_SIZE - 1U)) / DIRENT_SIZE; | |
| return ulIdx; | |
| } | |
| #endif /* REDCONF_API_POSIX == 1 */ | |