/* ----> 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 #if REDCONF_API_POSIX == 1 #include #include #include #include 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 */