/* ----> 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 */ | |