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