1 /* ----> DO NOT REMOVE THE FOLLOWING NOTICE <----
\r
3 Copyright (c) 2014-2015 Datalight, Inc.
\r
4 All Rights Reserved Worldwide.
\r
6 This program is free software; you can redistribute it and/or modify
\r
7 it under the terms of the GNU General Public License as published by
\r
8 the Free Software Foundation; use version 2 of the License.
\r
10 This program is distributed in the hope that it will be useful,
\r
11 but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty
\r
12 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 GNU General Public License for more details.
\r
15 You should have received a copy of the GNU General Public License along
\r
16 with this program; if not, write to the Free Software Foundation, Inc.,
\r
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
19 /* Businesses and individuals that for commercial or other reasons cannot
\r
20 comply with the terms of the GPLv2 license may obtain a commercial license
\r
21 before incorporating Reliance Edge into proprietary software for
\r
22 distribution in any form. Visit http://www.datalight.com/reliance-edge for
\r
26 @brief Implements path utilities for the POSIX-like API layer.
\r
30 #if REDCONF_API_POSIX == 1
\r
32 #include <redcoreapi.h>
\r
33 #include <redvolume.h>
\r
34 #include <redposix.h>
\r
35 #include "redpath.h"
\r
38 static bool IsRootDir(const char *pszLocalPath);
\r
39 static bool PathHasMoreNames(const char *pszPathIdx);
\r
42 /** @brief Split a path into its component parts: a volume and a volume-local
\r
45 @param pszPath The path to split.
\r
46 @param pbVolNum On successful return, if non-NULL, populated with
\r
47 the volume number extracted from the path.
\r
48 @param ppszLocalPath On successful return, populated with the
\r
49 volume-local path: the path stripped of any volume
\r
50 path prefixing. If this parameter is NULL, that
\r
51 indicates there should be no local path, and any
\r
52 characters beyond the prefix (other than path
\r
53 separators) are treated as an error.
\r
55 @return A negated ::REDSTATUS code indicating the operation result.
\r
57 @retval 0 Operation was successful.
\r
58 @retval -RED_EINVAL @p pszPath is `NULL`.
\r
59 @retval -RED_ENOENT @p pszPath could not be matched to any volume; or
\r
60 @p ppszLocalPath is NULL but @p pszPath includes a local
\r
63 REDSTATUS RedPathSplit(
\r
64 const char *pszPath,
\r
66 const char **ppszLocalPath)
\r
76 const char *pszLocalPath = pszPath;
\r
77 uint8_t bMatchVol = UINT8_MAX;
\r
78 uint32_t ulMatchLen = 0U;
\r
79 uint8_t bDefaultVolNum = UINT8_MAX;
\r
82 for(bVolNum = 0U; bVolNum < REDCONF_VOLUME_COUNT; bVolNum++)
\r
84 const char *pszPrefix = gaRedVolConf[bVolNum].pszPathPrefix;
\r
85 uint32_t ulPrefixLen = RedStrLen(pszPrefix);
\r
87 if(ulPrefixLen == 0U)
\r
89 /* A volume with a path prefix of an empty string is the
\r
90 default volume, used when the path does not match the
\r
91 prefix of any other volume.
\r
93 The default volume should only be found once. During
\r
94 initialization, RedCoreInit() ensures that all volume
\r
95 prefixes are unique (including empty prefixes).
\r
97 REDASSERT(bDefaultVolNum == UINT8_MAX);
\r
98 bDefaultVolNum = bVolNum;
\r
100 /* For a path to match, it must either be the prefix exactly, or
\r
101 be followed by a path separator character. Thus, with a volume
\r
102 prefix of "/foo", both "/foo" and "/foo/bar" are matches, but
\r
105 else if( (RedStrNCmp(pszPath, pszPrefix, ulPrefixLen) == 0)
\r
106 && ((pszPath[ulPrefixLen] == '\0') || (pszPath[ulPrefixLen] == REDCONF_PATH_SEPARATOR)))
\r
108 /* The length of this match should never exactly equal the
\r
109 length of a previous match: that would require a duplicate
\r
110 volume name, which should have been detected during init.
\r
112 REDASSERT(ulPrefixLen != ulMatchLen);
\r
114 /* If multiple prefixes match, the longest takes precedence.
\r
115 Thus, if there are two prefixes "Flash" and "Flash/Backup",
\r
116 the path "Flash/Backup/" will not be erroneously matched
\r
117 with the "Flash" volume.
\r
119 if(ulPrefixLen > ulMatchLen)
\r
121 bMatchVol = bVolNum;
\r
122 ulMatchLen = ulPrefixLen;
\r
127 /* No match, keep looking.
\r
132 if(bMatchVol != UINT8_MAX)
\r
134 /* The path matched a volume path prefix.
\r
136 bVolNum = bMatchVol;
\r
137 pszLocalPath = &pszPath[ulMatchLen];
\r
139 else if(bDefaultVolNum != UINT8_MAX)
\r
141 /* The path didn't match any of the prefixes, but one of the
\r
142 volumes has a path prefix of "", so an unprefixed path is
\r
143 assigned to that volume.
\r
145 bVolNum = bDefaultVolNum;
\r
146 REDASSERT(pszLocalPath == pszPath);
\r
150 /* The path cannot be assigned a volume.
\r
157 if(pbVolNum != NULL)
\r
159 *pbVolNum = bVolNum;
\r
162 if(ppszLocalPath != NULL)
\r
164 *ppszLocalPath = pszLocalPath;
\r
168 /* If no local path is expected, then the string should either
\r
169 terminate after the path prefix or the local path should name
\r
170 the root directory. Allowing path separators here means that
\r
171 red_mount("/data/") is OK with a path prefix of "/data".
\r
173 if(pszLocalPath[0U] != '\0')
\r
175 if(!IsRootDir(pszLocalPath))
\r
188 /** @brief Lookup the inode named by the given path.
\r
190 @param pszLocalPath The path to lookup; this is a local path, without any
\r
192 @param pulInode On successful return, populated with the number of the
\r
193 inode named by @p pszLocalPath.
\r
195 @return A negated ::REDSTATUS code indicating the operation result.
\r
197 @retval 0 Operation was successful.
\r
198 @retval -RED_EINVAL @p pszLocalPath is `NULL`; or @p pulInode is
\r
200 @retval -RED_EIO A disk I/O error occurred.
\r
201 @retval -RED_ENOENT @p pszLocalPath is an empty string; or
\r
202 @p pszLocalPath does not name an existing file
\r
204 @retval -RED_ENOTDIR A component of the path other than the last is
\r
206 @retval -RED_ENAMETOOLONG The length of a component of @p pszLocalPath is
\r
207 longer than #REDCONF_NAME_MAX.
\r
209 REDSTATUS RedPathLookup(
\r
210 const char *pszLocalPath,
\r
211 uint32_t *pulInode)
\r
215 if((pszLocalPath == NULL) || (pulInode == NULL))
\r
220 else if(pszLocalPath[0U] == '\0')
\r
224 else if(IsRootDir(pszLocalPath))
\r
227 *pulInode = INODE_ROOTDIR;
\r
232 const char *pszName;
\r
234 ret = RedPathToName(pszLocalPath, &ulPInode, &pszName);
\r
238 ret = RedCoreLookup(ulPInode, pszName, pulInode);
\r
246 /** @brief Given a path, return the parent inode number and a pointer to the
\r
247 last component in the path (the name).
\r
249 @param pszLocalPath The path to examine; this is a local path, without any
\r
251 @param pulPInode On successful return, populated with the inode number of
\r
252 the parent directory of the last component in the path.
\r
253 For example, with the path "a/b/c", populated with the
\r
254 inode number of "b".
\r
255 @param ppszName On successful return, populated with a pointer to the
\r
256 last component in the path. For example, with the path
\r
257 "a/b/c", populated with a pointer to "c".
\r
259 @return A negated ::REDSTATUS code indicating the operation result.
\r
261 @retval 0 Operation was successful.
\r
262 @retval -RED_EINVAL @p pszLocalPath is `NULL`; or @p pulPInode is
\r
263 `NULL`; or @p ppszName is `NULL`; or the path
\r
264 names the root directory.
\r
265 @retval -RED_EIO A disk I/O error occurred.
\r
266 @retval -RED_ENOENT @p pszLocalPath is an empty string; or a
\r
267 component of the path other than the last does
\r
269 @retval -RED_ENOTDIR A component of the path other than the last is
\r
271 @retval -RED_ENAMETOOLONG The length of a component of @p pszLocalPath is
\r
272 longer than #REDCONF_NAME_MAX.
\r
274 REDSTATUS RedPathToName(
\r
275 const char *pszLocalPath,
\r
276 uint32_t *pulPInode,
\r
277 const char **ppszName)
\r
281 if((pszLocalPath == NULL) || (pulPInode == NULL) || (ppszName == NULL))
\r
286 else if(IsRootDir(pszLocalPath))
\r
290 else if(pszLocalPath[0U] == '\0')
\r
296 uint32_t ulInode = INODE_ROOTDIR;
\r
297 uint32_t ulPInode = INODE_INVALID;
\r
298 uint32_t ulPathIdx = 0U;
\r
299 uint32_t ulLastNameIdx = 0U;
\r
305 uint32_t ulNameLen;
\r
307 /* Skip over path separators, to get pszLocalPath[ulPathIdx]
\r
308 pointing at the next name.
\r
310 while(pszLocalPath[ulPathIdx] == REDCONF_PATH_SEPARATOR)
\r
315 if(pszLocalPath[ulPathIdx] == '\0')
\r
320 /* Point ulLastNameIdx at the first character of the name; after
\r
321 we exit the loop, it will point at the first character of the
\r
322 last name in the path.
\r
324 ulLastNameIdx = ulPathIdx;
\r
326 /* Point ulPInode at the parent inode: either the root inode
\r
327 (first pass) or the inode of the previous name. After we exit
\r
328 the loop, this will point at the parent inode of the last name.
\r
330 ulPInode = ulInode;
\r
332 ulNameLen = RedNameLen(&pszLocalPath[ulPathIdx]);
\r
334 /* Lookup the inode of the name, unless we are at the last name in
\r
335 the path: we don't care whether the last name exists or not.
\r
337 if(PathHasMoreNames(&pszLocalPath[ulPathIdx + ulNameLen]))
\r
339 ret = RedCoreLookup(ulPInode, &pszLocalPath[ulPathIdx], &ulInode);
\r
342 /* Move on to the next path element.
\r
346 ulPathIdx += ulNameLen;
\r
353 *pulPInode = ulPInode;
\r
354 *ppszName = &pszLocalPath[ulLastNameIdx];
\r
362 /** @brief Determine whether a path names the root directory.
\r
364 @param pszLocalPath The path to examine; this is a local path, without any
\r
367 @return Returns whether @p pszLocalPath names the root directory.
\r
369 @retval true @p pszLocalPath names the root directory.
\r
370 @retval false @p pszLocalPath does not name the root directory.
\r
372 static bool IsRootDir(
\r
373 const char *pszLocalPath)
\r
377 if(pszLocalPath == NULL)
\r
384 uint32_t ulIdx = 0U;
\r
386 /* A string containing nothing but path separators (usually only one)
\r
387 names the root directory. An empty string does *not* name the root
\r
388 directory, since in POSIX empty strings typically elicit -RED_ENOENT
\r
391 while(pszLocalPath[ulIdx] == REDCONF_PATH_SEPARATOR)
\r
396 fRet = (ulIdx > 0U) && (pszLocalPath[ulIdx] == '\0');
\r
403 /** @brief Determine whether there are more names in a path.
\r
414 @param pszPathIdx The path to examine, incremented to the point of
\r
417 @return Returns whether there are more names in @p pszPathIdx.
\r
419 @retval true @p pszPathIdx has more names.
\r
420 @retval false @p pszPathIdx has no more names.
\r
422 static bool PathHasMoreNames(
\r
423 const char *pszPathIdx)
\r
427 if(pszPathIdx == NULL)
\r
434 uint32_t ulIdx = 0U;
\r
436 while(pszPathIdx[ulIdx] == REDCONF_PATH_SEPARATOR)
\r
441 fRet = pszPathIdx[ulIdx] != '\0';
\r
447 #endif /* REDCONF_API_POSIX */
\r