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