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