You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
446 lines
15 KiB
C
446 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 */
|