blob: ecdf0a7b8bee39f25d874cc307197d1bc3b307fd [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 path utilities for the POSIX-like API layer.
*/
#include <redfs.h>
#if REDCONF_API_POSIX == 1
#include <redcoreapi.h>
#include <redvolume.h>
#include <redposix.h>
#include <redpath.h>
static bool IsRootDir(const char *pszLocalPath);
static bool PathHasMoreNames(const char *pszPathIdx);
/** @brief Split a path into its component parts: a volume and a volume-local
path.
@param pszPath The path to split.
@param pbVolNum On successful return, if non-NULL, populated with
the volume number extracted from the path.
@param ppszLocalPath On successful return, populated with the
volume-local path: the path stripped of any volume
path prefixing. If this parameter is NULL, that
indicates there should be no local path, and any
characters beyond the prefix (other than path
separators) are treated as an error.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL @p pszPath is `NULL`.
@retval -RED_ENOENT @p pszPath could not be matched to any volume; or
@p ppszLocalPath is NULL but @p pszPath includes a local
path.
*/
REDSTATUS RedPathSplit(
const char *pszPath,
uint8_t *pbVolNum,
const char **ppszLocalPath)
{
REDSTATUS ret = 0;
if(pszPath == NULL)
{
ret = -RED_EINVAL;
}
else
{
const char *pszLocalPath = pszPath;
uint8_t bMatchVol = UINT8_MAX;
uint32_t ulMatchLen = 0U;
uint8_t bDefaultVolNum = UINT8_MAX;
uint8_t bVolNum;
for(bVolNum = 0U; bVolNum < REDCONF_VOLUME_COUNT; bVolNum++)
{
const char *pszPrefix = gaRedVolConf[bVolNum].pszPathPrefix;
uint32_t ulPrefixLen = RedStrLen(pszPrefix);
if(ulPrefixLen == 0U)
{
/* A volume with a path prefix of an empty string is the
default volume, used when the path does not match the
prefix of any other volume.
The default volume should only be found once. During
initialization, RedCoreInit() ensures that all volume
prefixes are unique (including empty prefixes).
*/
REDASSERT(bDefaultVolNum == UINT8_MAX);
bDefaultVolNum = bVolNum;
}
/* For a path to match, it must either be the prefix exactly, or
be followed by a path separator character. Thus, with a volume
prefix of "/foo", both "/foo" and "/foo/bar" are matches, but
"/foobar" is not.
*/
else if( (RedStrNCmp(pszPath, pszPrefix, ulPrefixLen) == 0)
&& ((pszPath[ulPrefixLen] == '\0') || (pszPath[ulPrefixLen] == REDCONF_PATH_SEPARATOR)))
{
/* The length of this match should never exactly equal the
length of a previous match: that would require a duplicate
volume name, which should have been detected during init.
*/
REDASSERT(ulPrefixLen != ulMatchLen);
/* If multiple prefixes match, the longest takes precedence.
Thus, if there are two prefixes "Flash" and "Flash/Backup",
the path "Flash/Backup/" will not be erroneously matched
with the "Flash" volume.
*/
if(ulPrefixLen > ulMatchLen)
{
bMatchVol = bVolNum;
ulMatchLen = ulPrefixLen;
}
}
else
{
/* No match, keep looking.
*/
}
}
if(bMatchVol != UINT8_MAX)
{
/* The path matched a volume path prefix.
*/
bVolNum = bMatchVol;
pszLocalPath = &pszPath[ulMatchLen];
}
else if(bDefaultVolNum != UINT8_MAX)
{
/* The path didn't match any of the prefixes, but one of the
volumes has a path prefix of "", so an unprefixed path is
assigned to that volume.
*/
bVolNum = bDefaultVolNum;
REDASSERT(pszLocalPath == pszPath);
}
else
{
/* The path cannot be assigned a volume.
*/
ret = -RED_ENOENT;
}
if(ret == 0)
{
if(pbVolNum != NULL)
{
*pbVolNum = bVolNum;
}
if(ppszLocalPath != NULL)
{
*ppszLocalPath = pszLocalPath;
}
else
{
/* If no local path is expected, then the string should either
terminate after the path prefix or the local path should name
the root directory. Allowing path separators here means that
red_mount("/data/") is OK with a path prefix of "/data".
*/
if(pszLocalPath[0U] != '\0')
{
if(!IsRootDir(pszLocalPath))
{
ret = -RED_ENOENT;
}
}
}
}
}
return ret;
}
/** @brief Lookup the inode named by the given path.
@param pszLocalPath The path to lookup; this is a local path, without any
volume prefix.
@param pulInode On successful return, populated with the number of the
inode named by @p pszLocalPath.
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL @p pszLocalPath is `NULL`; or @p pulInode is
`NULL`.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_ENOENT @p pszLocalPath is an empty string; or
@p pszLocalPath does not name an existing file
or directory.
@retval -RED_ENOTDIR A component of the path other than the last is
not a directory.
@retval -RED_ENAMETOOLONG The length of a component of @p pszLocalPath is
longer than #REDCONF_NAME_MAX.
*/
REDSTATUS RedPathLookup(
const char *pszLocalPath,
uint32_t *pulInode)
{
REDSTATUS ret;
if((pszLocalPath == NULL) || (pulInode == NULL))
{
REDERROR();
ret = -RED_EINVAL;
}
else if(pszLocalPath[0U] == '\0')
{
ret = -RED_ENOENT;
}
else if(IsRootDir(pszLocalPath))
{
ret = 0;
*pulInode = INODE_ROOTDIR;
}
else
{
uint32_t ulPInode;
const char *pszName;
ret = RedPathToName(pszLocalPath, &ulPInode, &pszName);
if(ret == 0)
{
ret = RedCoreLookup(ulPInode, pszName, pulInode);
}
}
return ret;
}
/** @brief Given a path, return the parent inode number and a pointer to the
last component in the path (the name).
@param pszLocalPath The path to examine; this is a local path, without any
volume prefix.
@param pulPInode On successful return, populated with the inode number of
the parent directory of the last component in the path.
For example, with the path "a/b/c", populated with the
inode number of "b".
@param ppszName On successful return, populated with a pointer to the
last component in the path. For example, with the path
"a/b/c", populated with a pointer to "c".
@return A negated ::REDSTATUS code indicating the operation result.
@retval 0 Operation was successful.
@retval -RED_EINVAL @p pszLocalPath is `NULL`; or @p pulPInode is
`NULL`; or @p ppszName is `NULL`; or the path
names the root directory.
@retval -RED_EIO A disk I/O error occurred.
@retval -RED_ENOENT @p pszLocalPath is an empty string; or a
component of the path other than the last does
not exist.
@retval -RED_ENOTDIR A component of the path other than the last is
not a directory.
@retval -RED_ENAMETOOLONG The length of a component of @p pszLocalPath is
longer than #REDCONF_NAME_MAX.
*/
REDSTATUS RedPathToName(
const char *pszLocalPath,
uint32_t *pulPInode,
const char **ppszName)
{
REDSTATUS ret;
if((pszLocalPath == NULL) || (pulPInode == NULL) || (ppszName == NULL))
{
REDERROR();
ret = -RED_EINVAL;
}
else if(IsRootDir(pszLocalPath))
{
ret = -RED_EINVAL;
}
else if(pszLocalPath[0U] == '\0')
{
ret = -RED_ENOENT;
}
else
{
uint32_t ulInode = INODE_ROOTDIR;
uint32_t ulPInode = INODE_INVALID;
uint32_t ulPathIdx = 0U;
uint32_t ulLastNameIdx = 0U;
ret = 0;
do
{
uint32_t ulNameLen;
/* Skip over path separators, to get pszLocalPath[ulPathIdx]
pointing at the next name.
*/
while(pszLocalPath[ulPathIdx] == REDCONF_PATH_SEPARATOR)
{
ulPathIdx++;
}
if(pszLocalPath[ulPathIdx] == '\0')
{
break;
}
/* Point ulLastNameIdx at the first character of the name; after
we exit the loop, it will point at the first character of the
last name in the path.
*/
ulLastNameIdx = ulPathIdx;
/* Point ulPInode at the parent inode: either the root inode
(first pass) or the inode of the previous name. After we exit
the loop, this will point at the parent inode of the last name.
*/
ulPInode = ulInode;
ulNameLen = RedNameLen(&pszLocalPath[ulPathIdx]);
/* Lookup the inode of the name, unless we are at the last name in
the path: we don't care whether the last name exists or not.
*/
if(PathHasMoreNames(&pszLocalPath[ulPathIdx + ulNameLen]))
{
ret = RedCoreLookup(ulPInode, &pszLocalPath[ulPathIdx], &ulInode);
}
/* Move on to the next path element.
*/
if(ret == 0)
{
ulPathIdx += ulNameLen;
}
}
while(ret == 0);
if(ret == 0)
{
*pulPInode = ulPInode;
*ppszName = &pszLocalPath[ulLastNameIdx];
}
}
return ret;
}
/** @brief Determine whether a path names the root directory.
@param pszLocalPath The path to examine; this is a local path, without any
volume prefix.
@return Returns whether @p pszLocalPath names the root directory.
@retval true @p pszLocalPath names the root directory.
@retval false @p pszLocalPath does not name the root directory.
*/
static bool IsRootDir(
const char *pszLocalPath)
{
bool fRet;
if(pszLocalPath == NULL)
{
REDERROR();
fRet = false;
}
else
{
uint32_t ulIdx = 0U;
/* A string containing nothing but path separators (usually only one)
names the root directory. An empty string does *not* name the root
directory, since in POSIX empty strings typically elicit -RED_ENOENT
errors.
*/
while(pszLocalPath[ulIdx] == REDCONF_PATH_SEPARATOR)
{
ulIdx++;
}
fRet = (ulIdx > 0U) && (pszLocalPath[ulIdx] == '\0');
}
return fRet;
}
/** @brief Determine whether there are more names in a path.
Example | Result
------- | ------
"" false
"/" false
"//" false
"a" true
"/a" true
"//a" true
@param pszPathIdx The path to examine, incremented to the point of
interest.
@return Returns whether there are more names in @p pszPathIdx.
@retval true @p pszPathIdx has more names.
@retval false @p pszPathIdx has no more names.
*/
static bool PathHasMoreNames(
const char *pszPathIdx)
{
bool fRet;
if(pszPathIdx == NULL)
{
REDERROR();
fRet = false;
}
else
{
uint32_t ulIdx = 0U;
while(pszPathIdx[ulIdx] == REDCONF_PATH_SEPARATOR)
{
ulIdx++;
}
fRet = pszPathIdx[ulIdx] != '\0';
}
return fRet;
}
#endif /* REDCONF_API_POSIX */