449 lines
15 KiB
C
449 lines
15 KiB
C
|
/* ----> 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 */
|
||
|
|