blob: 06c3a23eb0eb4552ba23075b53ddb94b281f4c61 [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 the entry-points to the core file system.
*/
#include <redfs.h>
#include <redcoreapi.h>
#include <redcore.h>
/* Minimum number of blocks needed for metadata on any volume: the master
block (1), the two metaroots (2), and one doubly-allocated inode (2),
resulting in 1 + 2 + 2 = 5.
*/
#define MINIMUM_METADATA_BLOCKS (5U)
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1)
static REDSTATUS CoreCreate(uint32_t ulPInode, const char *pszName, bool fDir, uint32_t *pulInode);
#endif
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_LINK == 1)
static REDSTATUS CoreLink(uint32_t ulPInode, const char *pszName, uint32_t ulInode);
#endif
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && ((REDCONF_API_POSIX_UNLINK == 1) || (REDCONF_API_POSIX_RMDIR == 1))
static REDSTATUS CoreUnlink(uint32_t ulPInode, const char *pszName);
#endif
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_RENAME == 1)
static REDSTATUS CoreRename(uint32_t ulSrcPInode, const char *pszSrcName, uint32_t ulDstPInode, const char *pszDstName);
#endif
#if REDCONF_READ_ONLY == 0
static REDSTATUS CoreFileWrite(uint32_t ulInode, uint64_t ullStart, uint32_t *pulLen, const void *pBuffer);
#endif
#if TRUNCATE_SUPPORTED
static REDSTATUS CoreFileTruncate(uint32_t ulInode, uint64_t ullSize);
#endif
VOLUME gaRedVolume[REDCONF_VOLUME_COUNT];
static COREVOLUME gaCoreVol[REDCONF_VOLUME_COUNT];
const VOLCONF * CONST_IF_ONE_VOLUME gpRedVolConf = &gaRedVolConf[0U];
VOLUME * CONST_IF_ONE_VOLUME gpRedVolume = &gaRedVolume[0U];
COREVOLUME * CONST_IF_ONE_VOLUME gpRedCoreVol = &gaCoreVol[0U];
METAROOT *gpRedMR = &gaCoreVol[0U].aMR[0U];
CONST_IF_ONE_VOLUME uint8_t gbRedVolNum = 0;
/** @brief Initialize the Reliance Edge file system driver.
Prepares the Reliance Edge file system driver to be used. Must be the first
Reliance Edge function to be invoked: no volumes can be mounted until the
driver has been initialized.
If this function is called when the Reliance Edge driver is already
initialized, the behavior is undefined.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
*/
REDSTATUS RedCoreInit(void)
{
REDSTATUS ret = 0;
uint8_t bVolNum;
#if REDCONF_OUTPUT == 1
static uint8_t bSignedOn = 0U; /* Whether the sign on has been printed. */
if(bSignedOn == 0U)
{
RedSignOn();
bSignedOn = 1U;
}
#else
/* Call RedSignOn() even when output is disabled, to force the copyright
text to be referenced and pulled into the program data.
*/
RedSignOn();
#endif
RedMemSet(gaRedVolume, 0U, sizeof(gaRedVolume));
RedMemSet(gaCoreVol, 0U, sizeof(gaCoreVol));
RedBufferInit();
for(bVolNum = 0U; bVolNum < REDCONF_VOLUME_COUNT; bVolNum++)
{
VOLUME *pVol = &gaRedVolume[bVolNum];
COREVOLUME *pCoreVol = &gaCoreVol[bVolNum];
const VOLCONF *pVolConf = &gaRedVolConf[bVolNum];
if( (pVolConf->ulSectorSize < SECTOR_SIZE_MIN)
|| ((REDCONF_BLOCK_SIZE % pVolConf->ulSectorSize) != 0U)
|| (pVolConf->ulInodeCount == 0U))
{
ret = -RED_EINVAL;
}
#if REDCONF_API_POSIX == 1
else if(pVolConf->pszPathPrefix == NULL)
{
ret = -RED_EINVAL;
}
else
{
#if REDCONF_VOLUME_COUNT > 1U
uint8_t bCmpVol;
/* Ensure there are no duplicate path prefixes. Check against all
previous volumes, which are already verified.
*/
for(bCmpVol = 0U; bCmpVol < bVolNum; bCmpVol++)
{
const char *pszCmpPathPrefix = gaRedVolConf[bCmpVol].pszPathPrefix;
if(RedStrCmp(pVolConf->pszPathPrefix, pszCmpPathPrefix) == 0)
{
ret = -RED_EINVAL;
break;
}
}
#endif
}
#endif
if(ret == 0)
{
pVol->bBlockSectorShift = 0U;
while((pVolConf->ulSectorSize << pVol->bBlockSectorShift) < REDCONF_BLOCK_SIZE)
{
pVol->bBlockSectorShift++;
}
/* This should always be true since the block size is confirmed to
be a power of two (checked at compile time) and above we ensured
that (REDCONF_BLOCK_SIZE % pVolConf->ulSectorSize) == 0.
*/
REDASSERT((pVolConf->ulSectorSize << pVol->bBlockSectorShift) == REDCONF_BLOCK_SIZE);
pVol->ulBlockCount = (uint32_t)(pVolConf->ullSectorCount >> pVol->bBlockSectorShift);
if(pVol->ulBlockCount < MINIMUM_METADATA_BLOCKS)
{
ret = -RED_EINVAL;
}
else
{
#if REDCONF_READ_ONLY == 0
pVol->ulTransMask = REDCONF_TRANSACT_DEFAULT;
#endif
pVol->ullMaxInodeSize = INODE_SIZE_MAX;
/* To understand the following code, note that the fixed-
location metadata is located at the start of the disk, in
the following order:
- Master block (1 block)
- Metaroots (2 blocks)
- External imap blocks (variable * 2 blocks)
- Inode blocks (pVolConf->ulInodeCount * 2 blocks)
*/
/* The imap needs bits for all inode and allocable blocks. If
that bitmap will fit into the metaroot, the inline imap is
used and there are no imap nodes on disk. The minus 3 is
there since the imap does not include bits for the master
block or metaroots.
*/
pCoreVol->fImapInline = (pVol->ulBlockCount - 3U) <= METAROOT_ENTRIES;
if(pCoreVol->fImapInline)
{
#if REDCONF_IMAP_INLINE == 1
pCoreVol->ulInodeTableStartBN = 3U;
#else
ret = -RED_EINVAL;
#endif
}
else
{
#if REDCONF_IMAP_EXTERNAL == 1
pCoreVol->ulImapStartBN = 3U;
/* The imap does not include bits for itself, so add two to
the number of imap entries for the two blocks of each
imap node. This allows us to divide up the remaining
space, making sure to round up so all data blocks are
covered.
*/
pCoreVol->ulImapNodeCount =
((pVol->ulBlockCount - 3U) + ((IMAPNODE_ENTRIES + 2U) - 1U)) / (IMAPNODE_ENTRIES + 2U);
pCoreVol->ulInodeTableStartBN = pCoreVol->ulImapStartBN + (pCoreVol->ulImapNodeCount * 2U);
#else
ret = -RED_EINVAL;
#endif
}
}
}
if(ret == 0)
{
pCoreVol->ulFirstAllocableBN = pCoreVol->ulInodeTableStartBN + (pVolConf->ulInodeCount * 2U);
if(pCoreVol->ulFirstAllocableBN > pVol->ulBlockCount)
{
/* We can get here if there is not enough space for the number
of configured inodes.
*/
ret = -RED_EINVAL;
}
else
{
pVol->ulBlocksAllocable = pVol->ulBlockCount - pCoreVol->ulFirstAllocableBN;
}
}
if(ret != 0)
{
break;
}
}
/* Make sure the configured endianness is correct.
*/
if(ret == 0)
{
uint16_t uValue = 0xFF00U;
uint8_t abBytes[2U];
RedMemCpy(abBytes, &uValue, sizeof(abBytes));
#if REDCONF_ENDIAN_BIG == 1
if(abBytes[0U] != 0xFFU)
#else
if(abBytes[0U] != 0x00U)
#endif
{
ret = -RED_EINVAL;
}
}
if(ret == 0)
{
ret = RedOsClockInit();
#if REDCONF_TASK_COUNT > 1U
if(ret == 0)
{
ret = RedOsMutexInit();
if(ret != 0)
{
(void)RedOsClockUninit();
}
}
#endif
}
return ret;
}
/** @brief Uninitialize the Reliance Edge file system driver.
Tears down the Reliance Edge file system driver. Cannot be used until all
Reliance Edge volumes are unmounted. A subsequent call to RedCoreInit()
will initialize the driver again.
The behavior of calling this function when the core is already uninitialized
is undefined.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBUSY At least one volume is still mounted.
*/
REDSTATUS RedCoreUninit(void)
{
REDSTATUS ret;
#if REDCONF_TASK_COUNT > 1U
ret = RedOsMutexUninit();
if(ret == 0)
#endif
{
ret = RedOsClockUninit();
}
return ret;
}
/** @brief Set the current volume.
All core APIs operate on the current volume. This call must precede all
core accesses.
@param bVolNum The volume number to access.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL @p bVolNum is an invalid volume number.
*/
REDSTATUS RedCoreVolSetCurrent(
uint8_t bVolNum)
{
REDSTATUS ret;
if(bVolNum >= REDCONF_VOLUME_COUNT)
{
ret = -RED_EINVAL;
}
else
{
#if REDCONF_VOLUME_COUNT > 1U
gbRedVolNum = bVolNum;
gpRedVolConf = &gaRedVolConf[bVolNum];
gpRedVolume = &gaRedVolume[bVolNum];
gpRedCoreVol = &gaCoreVol[bVolNum];
gpRedMR = &gpRedCoreVol->aMR[gpRedCoreVol->bCurMR];
#endif
ret = 0;
}
return ret;
}
#if FORMAT_SUPPORTED
/** @brief Format a file system volume.
Uses the statically defined volume configuration. After calling this
function, the volume needs to be mounted -- see RedCoreVolMount().
An error is returned if the volume is mounted.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBUSY Volume is mounted.
@retval -RED_EIO A disk I/O error occurred.
*/
REDSTATUS RedCoreVolFormat(void)
{
return RedVolFormat();
}
#endif /* FORMAT_SUPPORTED */
/** @brief Mount a file system volume.
Prepares the file system volume to be accessed. Mount will fail if the
volume has never been formatted, or if the on-disk format is inconsistent
with the compile-time configuration.
If the volume is already mounted, the behavior is undefined.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EIO Volume not formatted, improperly formatted, or corrupt.
*/
REDSTATUS RedCoreVolMount(void)
{
return RedVolMount();
}
/** @brief Unmount a file system volume.
This function discards the in-memory state for the file system and marks it
as unmounted. Subsequent attempts to access the volume will fail until the
volume is mounted again.
If unmount automatic transaction points are enabled, this function will
commit a transaction point prior to unmounting. If unmount automatic
transaction points are disabled, this function will unmount without
transacting, effectively discarding the working state.
If the volume is already unmounted, the behavior is undefined.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EIO I/O error during unmount automatic transaction point.
*/
REDSTATUS RedCoreVolUnmount(void)
{
REDSTATUS ret = 0;
#if REDCONF_READ_ONLY == 0
if(!gpRedVolume->fReadOnly && ((gpRedVolume->ulTransMask & RED_TRANSACT_UMOUNT) != 0U))
{
ret = RedVolTransact();
}
#endif
if(ret == 0)
{
ret = RedBufferDiscardRange(0U, gpRedVolume->ulBlockCount);
}
if(ret == 0)
{
ret = RedOsBDevClose(gbRedVolNum);
}
if(ret == 0)
{
gpRedVolume->fMounted = false;
}
return ret;
}
#if REDCONF_READ_ONLY == 0
/** @brief Commit a transaction point.
Reliance Edge is a transactional file system. All modifications, of both
metadata and filedata, are initially working state. A transaction point
is a process whereby the working state atomically becomes the committed
state, replacing the previous committed state. Whenever Reliance Edge is
mounted, including after power loss, the state of the file system after
mount is the most recent committed state. Nothing from the committed
state is ever missing, and nothing from the working state is ever included.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL The volume is not mounted.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EROFS The file system volume is read-only.
*/
REDSTATUS RedCoreVolTransact(void)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted)
{
ret = -RED_EINVAL;
}
else if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
ret = RedVolTransact();
}
return ret;
}
#endif /* REDCONF_READ_ONLY == 0 */
#if REDCONF_API_POSIX == 1
/** @brief Query file system status information.
@param pStatFS The buffer to populate with volume information.
@return A negated ::REDSTATUS code indicating the operation result.
@retval -RED_EINVAL Volume is not mounted; or @p pStatFS is `NULL`.
*/
REDSTATUS RedCoreVolStat(
REDSTATFS *pStatFS)
{
REDSTATUS ret;
if((pStatFS == NULL) || (!gpRedVolume->fMounted))
{
ret = -RED_EINVAL;
}
else
{
RedMemSet(pStatFS, 0U, sizeof(*pStatFS));
pStatFS->f_bsize = REDCONF_BLOCK_SIZE;
pStatFS->f_frsize = REDCONF_BLOCK_SIZE;
pStatFS->f_blocks = gpRedVolume->ulBlockCount;
#if RESERVED_BLOCKS > 0U
pStatFS->f_bfree = (gpRedMR->ulFreeBlocks > RESERVED_BLOCKS) ? (gpRedMR->ulFreeBlocks - RESERVED_BLOCKS) : 0U;
#else
pStatFS->f_bfree = gpRedMR->ulFreeBlocks;
#endif
pStatFS->f_bavail = pStatFS->f_bfree;
pStatFS->f_files = gpRedVolConf->ulInodeCount;
pStatFS->f_ffree = gpRedMR->ulFreeInodes;
pStatFS->f_favail = gpRedMR->ulFreeInodes;
pStatFS->f_flag = RED_ST_NOSUID;
#if REDCONF_READ_ONLY == 0
if(gpRedVolume->fReadOnly)
#endif
{
pStatFS->f_flag |= RED_ST_RDONLY;
}
pStatFS->f_namemax = REDCONF_NAME_MAX;
pStatFS->f_maxfsize = INODE_SIZE_MAX;
pStatFS->f_dev = gbRedVolNum;
ret = 0;
}
return ret;
}
#endif /* REDCONF_API_POSIX == 1 */
#if (REDCONF_READ_ONLY == 0) && ((REDCONF_API_POSIX == 1) || (REDCONF_API_FSE_TRANSMASKSET == 1))
/** @brief Update the transaction mask.
The following events are available when using the FSE API:
- #RED_TRANSACT_UMOUNT
- #RED_TRANSACT_WRITE
- #RED_TRANSACT_TRUNCATE
- #RED_TRANSACT_VOLFULL
The following events are available when using the POSIX-like API:
- #RED_TRANSACT_UMOUNT
- #RED_TRANSACT_CREAT
- #RED_TRANSACT_UNLINK
- #RED_TRANSACT_MKDIR
- #RED_TRANSACT_RENAME
- #RED_TRANSACT_LINK
- #RED_TRANSACT_CLOSE
- #RED_TRANSACT_WRITE
- #RED_TRANSACT_FSYNC
- #RED_TRANSACT_TRUNCATE
- #RED_TRANSACT_VOLFULL
The #RED_TRANSACT_MANUAL macro (by itself) may be used to disable all
automatic transaction events. The #RED_TRANSACT_MASK macro is a bitmask of
all transaction flags, excluding those representing excluded functionality.
Attempting to enable events for excluded functionality will result in an
error.
@param ulEventMask A bitwise-OR'd mask of automatic transaction events to
be set as the current transaction mode.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL The volume is not mounted; or @p ulEventMask contains
invalid bits.
@retval -RED_EROFS The file system volume is read-only.
*/
REDSTATUS RedCoreTransMaskSet(
uint32_t ulEventMask)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted || ((ulEventMask & RED_TRANSACT_MASK) != ulEventMask))
{
ret = -RED_EINVAL;
}
else if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
gpRedVolume->ulTransMask = ulEventMask;
ret = 0;
}
return ret;
}
#endif
#if (REDCONF_API_POSIX == 1) || (REDCONF_API_FSE_TRANSMASKGET == 1)
/** @brief Read the transaction mask.
If the volume is read-only, the returned event mask is always zero.
@param pulEventMask Populated with a bitwise-OR'd mask of automatic
transaction events which represent the current
transaction mode for the volume.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL The volume is not mounted; or @p pulEventMask is `NULL`.
*/
REDSTATUS RedCoreTransMaskGet(
uint32_t *pulEventMask)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted || (pulEventMask == NULL))
{
ret = -RED_EINVAL;
}
else
{
#if REDCONF_READ_ONLY == 1
*pulEventMask = 0U;
#else
*pulEventMask = gpRedVolume->ulTransMask;
#endif
ret = 0;
}
return ret;
}
#endif
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1)
/** @brief Create a file or directory.
@param ulPInode The inode number of the parent directory.
@param pszName A null-terminated name for the new inode.
@param fDir Whether to create a directory (true) or file (false).
@param pulInode On successful return, populated with the inode number of the
new file or directory.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL The volume is not mounted; or @p pszName is not
a valid name; or @p pulInode is `NULL`.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EROFS The file system volume is read-only.
@retval -RED_ENOTDIR @p ulPInode is not a directory.
@retval -RED_EBADF @p ulPInode is not a valid inode.
@retval -RED_ENOSPC There is not enough space on the volume to
createthe new directory entry; or the directory
is full.
@retval -RED_ENFILE No available inode slots.
@retval -RED_ENAMETOOLONG @p pszName is too long.
@retval -RED_EEXIST @p pszName already exists in @p ulPInode.
*/
REDSTATUS RedCoreCreate(
uint32_t ulPInode,
const char *pszName,
bool fDir,
uint32_t *pulInode)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted)
{
ret = -RED_EINVAL;
}
else if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
ret = CoreCreate(ulPInode, pszName, fDir, pulInode);
if( (ret == -RED_ENOSPC)
&& ((gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL) != 0U)
&& (gpRedCoreVol->ulAlmostFreeBlocks > 0U))
{
ret = RedVolTransact();
if(ret == 0)
{
ret = CoreCreate(ulPInode, pszName, fDir, pulInode);
}
}
if(ret == 0)
{
if(fDir && ((gpRedVolume->ulTransMask & RED_TRANSACT_MKDIR) != 0U))
{
ret = RedVolTransact();
}
else if(!fDir && ((gpRedVolume->ulTransMask & RED_TRANSACT_CREAT) != 0U))
{
ret = RedVolTransact();
}
else
{
/* No automatic transaction for this operation.
*/
}
}
}
return ret;
}
/** @brief Create a file or directory.
@param ulPInode The inode number of the parent directory.
@param pszName A null-terminated name for the new inode.
@param fDir Whether to create a directory (true) or file (false).
@param pulInode On successful return, populated with the inode number of the
new file or directory.
@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_EROFS The file system volume is read-only.
@retval -RED_ENOTDIR @p ulPInode is not a directory.
@retval -RED_EBADF @p ulPInode is not a valid inode.
@retval -RED_ENOSPC There is not enough space on the volume to
create the new directory entry; or the directory
is full.
@retval -RED_ENFILE No available inode slots.
@retval -RED_ENAMETOOLONG @p pszName is too long.
@retval -RED_EEXIST @p pszName already exists in @p ulPInode.
*/
static REDSTATUS CoreCreate(
uint32_t ulPInode,
const char *pszName,
bool fDir,
uint32_t *pulInode)
{
REDSTATUS ret;
if(pulInode == NULL)
{
ret = -RED_EINVAL;
}
else if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
CINODE pino;
pino.ulInode = ulPInode;
ret = RedInodeMount(&pino, FTYPE_DIR, false);
if(ret == 0)
{
CINODE ino;
ino.ulInode = INODE_INVALID;
ret = RedInodeCreate(&ino, ulPInode, fDir ? RED_S_IFDIR : RED_S_IFREG);
if(ret == 0)
{
ret = RedInodeBranch(&pino);
if(ret == 0)
{
ret = RedDirEntryCreate(&pino, pszName, ino.ulInode);
}
if(ret == 0)
{
*pulInode = ino.ulInode;
}
else
{
REDSTATUS ret2;
ret2 = RedInodeFree(&ino);
CRITICAL_ASSERT(ret2 == 0);
}
RedInodePut(&ino, 0U);
}
RedInodePut(&pino, (ret == 0) ? (uint8_t)(IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME) : 0U);
}
}
return ret;
}
#endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) */
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_LINK == 1)
/** @brief Create a hard link.
This creates an additional name (link) for @p ulInode. The new name refers
to the same file with the same contents. If a name is deleted, but the
underlying file has other names, the file continues to exist. The link
count (accessible via RedCoreStat()) indicates the number of names that a
file has. All of a file's names are on equal footing: there is nothing
special about the original name.
If @p ulInode names a directory, the operation will fail.
@param ulPInode The inode number of the parent directory.
@param pszName The null-terminated name for the new link.
@param ulInode The inode to create a hard link to.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulPInode is not a valid inode; or @p ulInode
is not a valid inode.
@retval -RED_EEXIST @p pszName resolves to an existing file.
@retval -RED_EINVAL The volume is not mounted; or @p pszName is
`NULL`.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EMLINK Creating the link would exceed the maximum link
count of @p ulInode.
@retval -RED_ENAMETOOLONG Attempting to create a link with a name that
exceeds the maximum name length.
@retval -RED_ENOSPC There is insufficient free space to expand the
directory that would contain the link.
@retval -RED_ENOTDIR @p ulPInode is not a directory.
@retval -RED_EPERM @p ulInode is a directory.
@retval -RED_EROFS The requested link requires writing in a
directory on a read-only file system.
*/
REDSTATUS RedCoreLink(
uint32_t ulPInode,
const char *pszName,
uint32_t ulInode)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted)
{
ret = -RED_EINVAL;
}
else if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
ret = CoreLink(ulPInode, pszName, ulInode);
if( (ret == -RED_ENOSPC)
&& ((gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL) != 0U)
&& (gpRedCoreVol->ulAlmostFreeBlocks > 0U))
{
ret = RedVolTransact();
if(ret == 0)
{
ret = CoreLink(ulPInode, pszName, ulInode);
}
}
if((ret == 0) && ((gpRedVolume->ulTransMask & RED_TRANSACT_LINK) != 0U))
{
ret = RedVolTransact();
}
}
return ret;
}
/** @brief Create a hard link.
@param ulPInode The inode number of the parent directory.
@param pszName The null-terminated name for the new link.
@param ulInode The inode to create a hard link to.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulPInode is not a valid inode; or @p ulInode
is not a valid inode.
@retval -RED_EEXIST @p pszName resolves to an existing file.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EMLINK Creating the link would exceed the maximum link
count of @p ulInode.
@retval -RED_ENAMETOOLONG Attempting to create a link with a name that
exceeds the maximum name length.
@retval -RED_ENOSPC There is insufficient free space to expand the
directory that would contain the link.
@retval -RED_ENOTDIR @p ulPInode is not a directory.
@retval -RED_EPERM @p ulInode is a directory.
@retval -RED_EROFS The requested link requires writing in a
directory on a read-only file system.
*/
static REDSTATUS CoreLink(
uint32_t ulPInode,
const char *pszName,
uint32_t ulInode)
{
REDSTATUS ret;
if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
CINODE pino;
pino.ulInode = ulPInode;
ret = RedInodeMount(&pino, FTYPE_DIR, false);
if(ret == 0)
{
CINODE ino;
ino.ulInode = ulInode;
ret = RedInodeMount(&ino, FTYPE_FILE, false);
/* POSIX specifies EPERM as the errno thrown when link() is given a
directory. Switch the errno returned if EISDIR was the return
value.
*/
if(ret == -RED_EISDIR)
{
ret = -RED_EPERM;
}
if(ret == 0)
{
if(ino.pInodeBuf->uNLink == UINT16_MAX)
{
ret = -RED_EMLINK;
}
else
{
ret = RedInodeBranch(&pino);
}
if(ret == 0)
{
ret = RedInodeBranch(&ino);
}
if(ret == 0)
{
ret = RedDirEntryCreate(&pino, pszName, ino.ulInode);
}
if(ret == 0)
{
ino.pInodeBuf->uNLink++;
}
RedInodePut(&ino, (ret == 0) ? IPUT_UPDATE_CTIME : 0U);
}
RedInodePut(&pino, (ret == 0) ? (uint8_t)(IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME) : 0U);
}
}
return ret;
}
#endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_LINK == 1) */
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && ((REDCONF_API_POSIX_UNLINK == 1) || (REDCONF_API_POSIX_RMDIR == 1))
/** @brief Delete a file or directory.
The given name is deleted and the link count of the corresponding inode is
decremented. If the link count falls to zero (no remaining hard links),
the inode will be deleted.
If the path names a directory which is not empty, the unlink will fail.
If the deletion frees data in the committed state, it will not return to
free space until after a transaction point. Similarly, if the inode was
part of the committed state, the inode slot will not be available until
after a transaction point.
This function can fail when the disk is full. To fix this, transact and
try again: Reliance Edge guarantees that it is possible to delete at least
one file or directory after a transaction point. If disk full automatic
transactions are enabled, this will happen automatically.
@param ulPInode The inode number of the parent directory.
@param pszName The null-terminated name of the file or directory to
delete.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulPInode is not a valid inode.
@retval -RED_EINVAL The volume is not mounted; or @p pszName is
`NULL`.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_ENAMETOOLONG @p pszName is too long.
@retval -RED_ENOENT @p pszName does not name an existing file or
directory.
@retval -RED_ENOTDIR @p ulPInode is not a directory.
@retval -RED_ENOSPC The file system does not have enough space to
modify the parent directory to perform the
deletion.
@retval -RED_ENOTEMPTY The inode refered to by @p pszName is a
directory which is not empty.
*/
REDSTATUS RedCoreUnlink(
uint32_t ulPInode,
const char *pszName)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted)
{
ret = -RED_EINVAL;
}
else if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
ret = CoreUnlink(ulPInode, pszName);
if( (ret == -RED_ENOSPC)
&& ((gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL) != 0U)
&& (gpRedCoreVol->ulAlmostFreeBlocks > 0U))
{
ret = RedVolTransact();
if(ret == 0)
{
ret = CoreUnlink(ulPInode, pszName);
}
}
if((ret == 0) && ((gpRedVolume->ulTransMask & RED_TRANSACT_UNLINK) != 0U))
{
ret = RedVolTransact();
}
}
return ret;
}
/** @brief Delete a file or directory.
@param ulPInode The inode number of the parent directory.
@param pszName The null-terminated name of the file or directory to
delete.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulPInode is not a valid inode.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_ENAMETOOLONG @p pszName is too long.
@retval -RED_ENOENT @p pszName does not name an existing file or
directory.
@retval -RED_ENOTDIR @p ulPInode is not a directory.
@retval -RED_ENOSPC The file system does not have enough space to
modify the parent directory to perform the
deletion.
@retval -RED_ENOTEMPTY The inode refered to by @p pszName is a
directory which is not empty.
*/
static REDSTATUS CoreUnlink(
uint32_t ulPInode,
const char *pszName)
{
REDSTATUS ret;
if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
CINODE pino;
pino.ulInode = ulPInode;
ret = RedInodeMount(&pino, FTYPE_DIR, false);
if(ret == 0)
{
uint32_t ulDeleteIdx;
uint32_t ulInode;
ret = RedDirEntryLookup(&pino, pszName, &ulDeleteIdx, &ulInode);
if(ret == 0)
{
ret = RedInodeBranch(&pino);
}
if(ret == 0)
{
CINODE ino;
ino.ulInode = ulInode;
ret = RedInodeMount(&ino, FTYPE_EITHER, false);
if(ret == 0)
{
if(ino.fDirectory && (ino.pInodeBuf->ullSize > 0U))
{
ret = -RED_ENOTEMPTY;
}
else
{
#if RESERVED_BLOCKS > 0U
gpRedCoreVol->fUseReservedBlocks = true;
#endif
ret = RedDirEntryDelete(&pino, ulDeleteIdx);
#if RESERVED_BLOCKS > 0U
gpRedCoreVol->fUseReservedBlocks = false;
#endif
if(ret == 0)
{
/* If the inode is deleted, buffers are needed to
read all of the indirects and free the data
blocks. Before doing that, to reduce the
minimum number of buffers needed to complete the
unlink, release the parent directory inode
buffers which are no longer needed.
*/
RedInodePutCoord(&pino);
ret = RedInodeLinkDec(&ino);
CRITICAL_ASSERT(ret == 0);
}
}
RedInodePut(&ino, (ret == 0) ? IPUT_UPDATE_CTIME : 0U);
}
}
RedInodePut(&pino, (ret == 0) ? (uint8_t)(IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME) : 0U);
}
}
return ret;
}
#endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && ((REDCONF_API_POSIX_UNLINK == 1) || (REDCONF_API_POSIX_RMDIR == 1)) */
#if REDCONF_API_POSIX == 1
/** @brief Look up the inode number of a file or directory.
@param ulPInode The inode number of the parent directory.
@param pszName The null-terminated name of the file or directory to look
up.
@param pulInode On successful return, populated with the inode number named
by @p pszName.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulPInode is not a valid inode.
@retval -RED_EINVAL The volume is not mounted; @p pszName is `NULL`; or
@p pulInode is `NULL`.
@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 ulPInode is not a directory.
*/
REDSTATUS RedCoreLookup(
uint32_t ulPInode,
const char *pszName,
uint32_t *pulInode)
{
REDSTATUS ret;
if((pulInode == NULL) || !gpRedVolume->fMounted)
{
ret = -RED_EINVAL;
}
else
{
CINODE ino;
ino.ulInode = ulPInode;
ret = RedInodeMount(&ino, FTYPE_DIR, false);
if(ret == 0)
{
ret = RedDirEntryLookup(&ino, pszName, NULL, pulInode);
RedInodePut(&ino, 0U);
}
}
return ret;
}
#endif /* REDCONF_API_POSIX == 1 */
#if (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_RENAME == 1)
/** @brief Rename a file or directory.
If @p pszDstName names an existing file or directory, the behavior depends
on the configuration. If #REDCONF_RENAME_ATOMIC is false, and if the
destination name exists, this function always fails with -RED_EEXIST.
If #REDCONF_RENAME_ATOMIC is true, and if the new name exists, then in one
atomic operation, the destination name is unlinked and the source name is
renamed to the destination name. Both names must be of the same type (both
files or both directories). As with RedCoreUnlink(), if the destination
name is a directory, it must be empty. The major exception to this
behavior is that if both names are links to the same inode, then the rename
does nothing and both names continue to exist.
If the rename deletes the old destination, it may free data in the
committed state, which will not return to free space until after a
transaction point. Similarly, if the deleted inode was part of the
committed state, the inode slot will not be available until after a
transaction point.
@param ulSrcPInode The inode number of the parent directory of the file or
directory to rename.
@param pszSrcName The name of the file or directory to rename.
@param ulDstPInode The new parent directory inode number of the file or
directory after the rename.
@param pszDstName The new name of the file or directory after the rename.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulSrcPInode is not a valid inode number; or
@p ulDstPInode is not a valid inode number.
@retval -RED_EEXIST #REDCONF_RENAME_POSIX is false and the
destination name exists.
@retval -RED_EINVAL The volume is not mounted; @p pszSrcName is
`NULL`; or @p pszDstName is `NULL`.
@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 ulSrcPInode is not a directory; or
@p ulDstPInode 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 RedCoreRename(
uint32_t ulSrcPInode,
const char *pszSrcName,
uint32_t ulDstPInode,
const char *pszDstName)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted)
{
ret = -RED_EINVAL;
}
else if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
ret = CoreRename(ulSrcPInode, pszSrcName, ulDstPInode, pszDstName);
if( (ret == -RED_ENOSPC)
&& ((gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL) != 0U)
&& (gpRedCoreVol->ulAlmostFreeBlocks > 0U))
{
ret = RedVolTransact();
if(ret == 0)
{
ret = CoreRename(ulSrcPInode, pszSrcName, ulDstPInode, pszDstName);
}
}
if((ret == 0) && ((gpRedVolume->ulTransMask & RED_TRANSACT_RENAME) != 0U))
{
ret = RedVolTransact();
}
}
return ret;
}
/** @brief Rename a file or directory.
@param ulSrcPInode The inode number of the parent directory of the file or
directory to rename.
@param pszSrcName The name of the file or directory to rename.
@param ulDstPInode The new parent directory inode number of the file or
directory after the rename.
@param pszDstName The new name of the file or directory after the rename.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulSrcPInode is not a valid inode number; or
@p ulDstPInode is not a valid inode number.
@retval -RED_EEXIST #REDCONF_RENAME_POSIX is false and the
destination name exists.
@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 ulSrcPInode is not a directory; or
@p ulDstPInode 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.
*/
static REDSTATUS CoreRename(
uint32_t ulSrcPInode,
const char *pszSrcName,
uint32_t ulDstPInode,
const char *pszDstName)
{
REDSTATUS ret;
if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
bool fUpdateTimestamps = false;
CINODE SrcPInode;
SrcPInode.ulInode = ulSrcPInode;
ret = RedInodeMount(&SrcPInode, FTYPE_DIR, true);
if(ret == 0)
{
CINODE DstPInode;
CINODE *pDstPInode;
if(ulSrcPInode == ulDstPInode)
{
pDstPInode = &SrcPInode;
}
else
{
pDstPInode = &DstPInode;
DstPInode.ulInode = ulDstPInode;
ret = RedInodeMount(pDstPInode, FTYPE_DIR, true);
}
if(ret == 0)
{
/* Initialize these to zero so we can unconditionally put them,
even if RedDirEntryRename() fails before mounting them.
*/
CINODE SrcInode = {0U};
CINODE DstInode = {0U};
ret = RedDirEntryRename(&SrcPInode, pszSrcName, &SrcInode, pDstPInode, pszDstName, &DstInode);
#if REDCONF_RENAME_ATOMIC == 1
if((ret == 0) && (DstInode.ulInode != INODE_INVALID) && (DstInode.ulInode != SrcInode.ulInode))
{
/* If the inode is deleted, buffers are needed to read all
of the indirects and free the data blocks. Before doing
that, to reduce the minimum number of buffers needed to
complete the rename, release parent directory inode
buffers which are no longer needed.
*/
RedInodePutCoord(&SrcPInode);
RedInodePutCoord(pDstPInode);
ret = RedInodeLinkDec(&DstInode);
CRITICAL_ASSERT(ret == 0);
}
if((ret == 0) && (DstInode.ulInode != SrcInode.ulInode))
#else
if(ret == 0)
#endif
{
fUpdateTimestamps = true;
}
#if REDCONF_RENAME_ATOMIC == 1
RedInodePut(&DstInode, 0U);
#endif
/* POSIX says updating ctime for the source inode is optional,
but searching around it looks like this is common for Linux
and other Unix file systems.
*/
RedInodePut(&SrcInode, fUpdateTimestamps ? IPUT_UPDATE_CTIME : 0U);
RedInodePut(pDstPInode, fUpdateTimestamps ? (uint8_t)(IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME) : 0U);
}
}
RedInodePut(&SrcPInode, fUpdateTimestamps ? (uint8_t)(IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME) : 0U);
}
return ret;
}
#endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_RENAME == 1) */
#if REDCONF_API_POSIX == 1
/** @brief Get the status of a file or directory.
See the ::REDSTAT type for the details of the information returned.
@param ulInode The inode number of the file or directory whose information
is to be retrieved.
@param pStat Pointer to a ::REDSTAT buffer to populate.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulInode is not a valid inode.
@retval -RED_EINVAL The volume is not mounted; @p pStat is `NULL`.
@retval -RED_EIO A disk I/O error occurred.
*/
REDSTATUS RedCoreStat(
uint32_t ulInode,
REDSTAT *pStat)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted || (pStat == NULL))
{
ret = -RED_EINVAL;
}
else
{
CINODE ino;
ino.ulInode = ulInode;
ret = RedInodeMount(&ino, FTYPE_EITHER, false);
if(ret == 0)
{
RedMemSet(pStat, 0U, sizeof(*pStat));
pStat->st_dev = gbRedVolNum;
pStat->st_ino = ulInode;
pStat->st_mode = ino.pInodeBuf->uMode;
#if REDCONF_API_POSIX_LINK == 1
pStat->st_nlink = ino.pInodeBuf->uNLink;
#else
pStat->st_nlink = 1U;
#endif
pStat->st_size = ino.pInodeBuf->ullSize;
#if REDCONF_INODE_TIMESTAMPS == 1
pStat->st_atime = ino.pInodeBuf->ulATime;
pStat->st_mtime = ino.pInodeBuf->ulMTime;
pStat->st_ctime = ino.pInodeBuf->ulCTime;
#endif
#if REDCONF_INODE_BLOCKS == 1
pStat->st_blocks = ino.pInodeBuf->ulBlocks;
#endif
RedInodePut(&ino, 0U);
}
}
return ret;
}
#endif /* REDCONF_API_POSIX == 1 */
#if REDCONF_API_FSE == 1
/** @brief Get the size of a file.
@param ulInode The inode number of the file whose size is to be retrieved.
@param pullSize On successful exit, populated with the file size.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulInode is not a valid inode.
@retval -RED_EINVAL The volume is not mounted; @p pullSize is `NULL`.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EISDIR @p ulInode is a directory inode.
*/
REDSTATUS RedCoreFileSizeGet(
uint32_t ulInode,
uint64_t *pullSize)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted || (pullSize == NULL))
{
ret = -RED_EINVAL;
}
else
{
CINODE ino;
ino.ulInode = ulInode;
ret = RedInodeMount(&ino, FTYPE_FILE, false);
if(ret == 0)
{
*pullSize = ino.pInodeBuf->ullSize;
RedInodePut(&ino, 0U);
}
}
return ret;
}
#endif /* REDCONF_API_FSE == 1 */
/** @brief Read from a file.
Data which has not yet been written, but which is before the end-of-file
(sparse data), shall read as zeroes. A short read -- where the number of
bytes read is less than requested -- indicates that the requested read was
partially or, if zero bytes were read, entirely beyond the end-of-file.
If @p ullStart is at or beyond the maximum file size, it is treated like
any other read entirely beyond the end-of-file: no data is read and
@p pulLen is populated with zero.
@param ulInode The inode number of the file to read.
@param ullStart The file offset to read from.
@param pulLen On entry, contains the number of bytes to read; on
successful exit, contains the number of bytes actually
read.
@param pBuffer The buffer to populate with the data read. Must be big
enough for the read request.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulInode is not a valid inode number.
@retval -RED_EINVAL The volume is not mounted; or @p pBuffer is `NULL`.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EISDIR The inode is a directory inode.
*/
REDSTATUS RedCoreFileRead(
uint32_t ulInode,
uint64_t ullStart,
uint32_t *pulLen,
void *pBuffer)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted || (pulLen == NULL))
{
ret = -RED_EINVAL;
}
else
{
#if (REDCONF_ATIME == 1) && (REDCONF_READ_ONLY == 0)
bool fUpdateAtime = (*pulLen > 0U) && !gpRedVolume->fReadOnly;
#else
bool fUpdateAtime = false;
#endif
CINODE ino;
ino.ulInode = ulInode;
ret = RedInodeMount(&ino, FTYPE_FILE, fUpdateAtime);
if(ret == 0)
{
ret = RedInodeDataRead(&ino, ullStart, pulLen, pBuffer);
#if (REDCONF_ATIME == 1) && (REDCONF_READ_ONLY == 0)
RedInodePut(&ino, ((ret == 0) && fUpdateAtime) ? IPUT_UPDATE_ATIME : 0U);
#else
RedInodePut(&ino, 0U);
#endif
}
}
return ret;
}
#if REDCONF_READ_ONLY == 0
/** @brief Write to a file.
If the write extends beyond the end-of-file, the file size will be
increased.
A short write -- where the number of bytes written is less than requested
-- indicates either that the file system ran out of space but was still
able to write some of the request; or that the request would have caused
the file to exceed the maximum file size, but some of the data could be
written prior to the file size limit.
If an error is returned, either none of the data was written or a critical
error occurred (like an I/O error) and the file system volume will be
read-only.
@param ulInode The file number of the file to write.
@param ullStart The file offset to write at.
@param pulLen On entry, the number of bytes to write; on successful exit,
the number of bytes actually written.
@param pBuffer The buffer containing the data to be written. Must big
enough for the write request.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulInode is not a valid file number.
@retval -RED_EFBIG No data can be written to the given file offset since
the resulting file size would exceed the maximum file
size.
@retval -RED_EINVAL The volume is not mounted; or @p pBuffer is `NULL`.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EISDIR The inode is a directory inode.
@retval -RED_ENOSPC No data can be written because there is insufficient
free space.
@retval -RED_EROFS The file system volume is read-only.
*/
REDSTATUS RedCoreFileWrite(
uint32_t ulInode,
uint64_t ullStart,
uint32_t *pulLen,
const void *pBuffer)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted)
{
ret = -RED_EINVAL;
}
else if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
ret = CoreFileWrite(ulInode, ullStart, pulLen, pBuffer);
if( (ret == -RED_ENOSPC)
&& ((gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL) != 0U)
&& (gpRedCoreVol->ulAlmostFreeBlocks > 0U))
{
ret = RedVolTransact();
if(ret == 0)
{
ret = CoreFileWrite(ulInode, ullStart, pulLen, pBuffer);
}
}
if((ret == 0) && ((gpRedVolume->ulTransMask & RED_TRANSACT_WRITE) != 0U))
{
ret = RedVolTransact();
}
}
return ret;
}
/** @brief Write to a file.
@param ulInode The file number of the file to write.
@param ullStart The file offset to write at.
@param pulLen On entry, the number of bytes to write; on successful exit,
the number of bytes actually written.
@param pBuffer The buffer containing the data to be written. Must big
enough for the write request.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulInode is not a valid file number.
@retval -RED_EFBIG No data can be written to the given file offset since
the resulting file size would exceed the maximum file
size.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EISDIR The inode is a directory inode.
@retval -RED_ENOSPC No data can be written because there is insufficient
free space.
@retval -RED_EROFS The file system volume is read-only.
*/
static REDSTATUS CoreFileWrite(
uint32_t ulInode,
uint64_t ullStart,
uint32_t *pulLen,
const void *pBuffer)
{
REDSTATUS ret;
if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
CINODE ino;
ino.ulInode = ulInode;
ret = RedInodeMount(&ino, FTYPE_FILE, true);
if(ret == 0)
{
ret = RedInodeDataWrite(&ino, ullStart, pulLen, pBuffer);
RedInodePut(&ino, (ret == 0) ? (uint8_t)(IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME) : 0U);
}
}
return ret;
}
#endif /* REDCONF_READ_ONLY == 0 */
#if TRUNCATE_SUPPORTED
/** @brief Set the file size.
Allows the file size to be increased, decreased, or to remain the same. If
the file size is increased, the new area is sparse (will read as zeroes).
If the file size is decreased, the data beyond the new end-of-file will
return to free space once it is no longer part of the committed state
(either immediately or after the next transaction point).
@param ulInode The inode of the file to truncate.
@param ullSize The new file size, in bytes.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulInode is not a valid inode number.
@retval -RED_EFBIG @p ullSize exceeds the maximum file size.
@retval -RED_EINVAL The volume is not mounted.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EISDIR The inode is a directory inode.
@retval -RED_ENOSPC Insufficient free space to perform the truncate.
@retval -RED_EROFS The file system volume is read-only.
*/
REDSTATUS RedCoreFileTruncate(
uint32_t ulInode,
uint64_t ullSize)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted)
{
ret = -RED_EINVAL;
}
else if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
ret = CoreFileTruncate(ulInode, ullSize);
if( (ret == -RED_ENOSPC)
&& ((gpRedVolume->ulTransMask & RED_TRANSACT_VOLFULL) != 0U)
&& (gpRedCoreVol->ulAlmostFreeBlocks > 0U))
{
ret = RedVolTransact();
if(ret == 0)
{
ret = CoreFileTruncate(ulInode, ullSize);
}
}
if((ret == 0) && ((gpRedVolume->ulTransMask & RED_TRANSACT_TRUNCATE) != 0U))
{
ret = RedVolTransact();
}
}
return ret;
}
/** @brief Set the file size.
@param ulInode The inode of the file to truncate.
@param ullSize The new file size, in bytes.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulInode is not a valid inode number.
@retval -RED_EFBIG @p ullSize exceeds the maximum file size.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_EISDIR The inode is a directory inode.
@retval -RED_ENOSPC Insufficient free space to perform the truncate.
@retval -RED_EROFS The file system volume is read-only.
*/
static REDSTATUS CoreFileTruncate(
uint32_t ulInode,
uint64_t ullSize)
{
REDSTATUS ret;
if(gpRedVolume->fReadOnly)
{
ret = -RED_EROFS;
}
else
{
CINODE ino;
ino.ulInode = ulInode;
ret = RedInodeMount(&ino, FTYPE_FILE, true);
if(ret == 0)
{
#if RESERVED_BLOCKS > 0U
gpRedCoreVol->fUseReservedBlocks = (ullSize < ino.pInodeBuf->ullSize);
#endif
ret = RedInodeDataTruncate(&ino, ullSize);
#if RESERVED_BLOCKS > 0U
gpRedCoreVol->fUseReservedBlocks = false;
#endif
RedInodePut(&ino, (ret == 0) ? (uint8_t)(IPUT_UPDATE_MTIME | IPUT_UPDATE_CTIME) : 0U);
}
}
return ret;
}
#endif /* TRUNCATE_SUPPORTED */
#if (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_READDIR == 1)
/** @brief Read from a directory.
If files are added to the directory after it is opened, the new files may
or may not be returned by this function. If files are deleted, the deleted
files will not be returned.
@param ulInode The directory inode to read from.
@param pulPos A token which stores the position within the directory. To
read from the beginning of the directory, populate with
zero.
@param pszName Pointer to a buffer which must be big enough to store a
maximum size name, including a null terminator. On
successful exit, populated with the name of the next
directory entry.
@param pulInode On successful return, populated with the inode number of the
next directory entry.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EBADF @p ulInode is not a valid inode number.
@retval -RED_EINVAL The volume is not mounted.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_ENOENT There are no more entries in the directory.
@retval -RED_ENOTDIR @p ulInode refers to a file.
*/
REDSTATUS RedCoreDirRead(
uint32_t ulInode,
uint32_t *pulPos,
char *pszName,
uint32_t *pulInode)
{
REDSTATUS ret;
if(!gpRedVolume->fMounted)
{
ret = -RED_EINVAL;
}
else
{
CINODE ino;
ino.ulInode = ulInode;
ret = RedInodeMount(&ino, FTYPE_DIR, false);
if(ret == 0)
{
ret = RedDirEntryRead(&ino, pulPos, pszName, pulInode);
#if (REDCONF_ATIME == 1) && (REDCONF_READ_ONLY == 0)
if((ret == 0) && !gpRedVolume->fReadOnly)
{
ret = RedInodeBranch(&ino);
}
RedInodePut(&ino, ((ret == 0) && !gpRedVolume->fReadOnly) ? IPUT_UPDATE_ATIME : 0U);
#else
RedInodePut(&ino, 0U);
#endif
}
}
return ret;
}
#endif /* (REDCONF_API_POSIX == 1) && (REDCONF_API_POSIX_READDIR == 1) */