blob: cfda1c1aa235011b3db8604f865a3c72f4483fd5 [file] [log] [blame]
/* ----> 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 */