1 /* ----> DO NOT REMOVE THE FOLLOWING NOTICE <---- 2 * 3 * Copyright (c) 2014-2015 Datalight, Inc. 4 * All Rights Reserved Worldwide. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; use version 2 of the License. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but "AS-IS," WITHOUT ANY WARRANTY; without even the implied warranty 12 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 */ 19 20 /* Businesses and individuals that for commercial or other reasons cannot 21 * comply with the terms of the GPLv2 license may obtain a commercial license 22 * before incorporating Reliance Edge into proprietary software for 23 * distribution in any form. Visit http://www.datalight.com/reliance-edge for 24 * more information. 25 */ 26 27 /** @file 28 * @brief Implements path utilities for the POSIX-like API layer. 29 */ 30 #include <redfs.h> 31 32 #if REDCONF_API_POSIX == 1 33 34 #include <redcoreapi.h> 35 #include <redvolume.h> 36 #include <redposix.h> 37 #include <redpath.h> 38 39 40 static bool IsRootDir( const char * pszLocalPath ); 41 static bool PathHasMoreNames( const char * pszPathIdx ); 42 43 44 /** @brief Split a path into its component parts: a volume and a volume-local 45 * path. 46 * 47 * @param pszPath The path to split. 48 * @param pbVolNum On successful return, if non-NULL, populated with 49 * the volume number extracted from the path. 50 * @param ppszLocalPath On successful return, populated with the 51 * volume-local path: the path stripped of any volume 52 * path prefixing. If this parameter is NULL, that 53 * indicates there should be no local path, and any 54 * characters beyond the prefix (other than path 55 * separators) are treated as an error. 56 * 57 * @return A negated ::REDSTATUS code indicating the operation result. 58 * 59 * @retval 0 Operation was successful. 60 * @retval -RED_EINVAL @p pszPath is `NULL`. 61 * @retval -RED_ENOENT @p pszPath could not be matched to any volume; or 62 * @p ppszLocalPath is NULL but @p pszPath includes a local 63 * path. 64 */ RedPathSplit(const char * pszPath,uint8_t * pbVolNum,const char ** ppszLocalPath)65 REDSTATUS RedPathSplit( const char * pszPath, 66 uint8_t * pbVolNum, 67 const char ** ppszLocalPath ) 68 { 69 REDSTATUS ret = 0; 70 71 if( pszPath == NULL ) 72 { 73 ret = -RED_EINVAL; 74 } 75 else 76 { 77 const char * pszLocalPath = pszPath; 78 uint8_t bMatchVol = UINT8_MAX; 79 uint32_t ulMatchLen = 0U; 80 uint8_t bDefaultVolNum = UINT8_MAX; 81 uint8_t bVolNum; 82 83 for( bVolNum = 0U; bVolNum < REDCONF_VOLUME_COUNT; bVolNum++ ) 84 { 85 const char * pszPrefix = gaRedVolConf[ bVolNum ].pszPathPrefix; 86 uint32_t ulPrefixLen = RedStrLen( pszPrefix ); 87 88 if( ulPrefixLen == 0U ) 89 { 90 /* A volume with a path prefix of an empty string is the 91 * default volume, used when the path does not match the 92 * prefix of any other volume. 93 * 94 * The default volume should only be found once. During 95 * initialization, RedCoreInit() ensures that all volume 96 * prefixes are unique (including empty prefixes). 97 */ 98 REDASSERT( bDefaultVolNum == UINT8_MAX ); 99 bDefaultVolNum = bVolNum; 100 } 101 102 /* For a path to match, it must either be the prefix exactly, or 103 * be followed by a path separator character. Thus, with a volume 104 * prefix of "/foo", both "/foo" and "/foo/bar" are matches, but 105 * "/foobar" is not. 106 */ 107 else if( ( RedStrNCmp( pszPath, pszPrefix, ulPrefixLen ) == 0 ) && 108 ( ( pszPath[ ulPrefixLen ] == '\0' ) || ( pszPath[ ulPrefixLen ] == REDCONF_PATH_SEPARATOR ) ) ) 109 { 110 /* The length of this match should never exactly equal the 111 * length of a previous match: that would require a duplicate 112 * volume name, which should have been detected during init. 113 */ 114 REDASSERT( ulPrefixLen != ulMatchLen ); 115 116 /* If multiple prefixes match, the longest takes precedence. 117 * Thus, if there are two prefixes "Flash" and "Flash/Backup", 118 * the path "Flash/Backup/" will not be erroneously matched 119 * with the "Flash" volume. 120 */ 121 if( ulPrefixLen > ulMatchLen ) 122 { 123 bMatchVol = bVolNum; 124 ulMatchLen = ulPrefixLen; 125 } 126 } 127 else 128 { 129 /* No match, keep looking. 130 */ 131 } 132 } 133 134 if( bMatchVol != UINT8_MAX ) 135 { 136 /* The path matched a volume path prefix. 137 */ 138 bVolNum = bMatchVol; 139 pszLocalPath = &pszPath[ ulMatchLen ]; 140 } 141 else if( bDefaultVolNum != UINT8_MAX ) 142 { 143 /* The path didn't match any of the prefixes, but one of the 144 * volumes has a path prefix of "", so an unprefixed path is 145 * assigned to that volume. 146 */ 147 bVolNum = bDefaultVolNum; 148 REDASSERT( pszLocalPath == pszPath ); 149 } 150 else 151 { 152 /* The path cannot be assigned a volume. 153 */ 154 ret = -RED_ENOENT; 155 } 156 157 if( ret == 0 ) 158 { 159 if( pbVolNum != NULL ) 160 { 161 *pbVolNum = bVolNum; 162 } 163 164 if( ppszLocalPath != NULL ) 165 { 166 *ppszLocalPath = pszLocalPath; 167 } 168 else 169 { 170 /* If no local path is expected, then the string should either 171 * terminate after the path prefix or the local path should name 172 * the root directory. Allowing path separators here means that 173 * red_mount("/data/") is OK with a path prefix of "/data". 174 */ 175 if( pszLocalPath[ 0U ] != '\0' ) 176 { 177 if( !IsRootDir( pszLocalPath ) ) 178 { 179 ret = -RED_ENOENT; 180 } 181 } 182 } 183 } 184 } 185 186 return ret; 187 } 188 189 190 /** @brief Lookup the inode named by the given path. 191 * 192 * @param pszLocalPath The path to lookup; this is a local path, without any 193 * volume prefix. 194 * @param pulInode On successful return, populated with the number of the 195 * inode named by @p pszLocalPath. 196 * 197 * @return A negated ::REDSTATUS code indicating the operation result. 198 * 199 * @retval 0 Operation was successful. 200 * @retval -RED_EINVAL @p pszLocalPath is `NULL`; or @p pulInode is 201 * `NULL`. 202 * @retval -RED_EIO A disk I/O error occurred. 203 * @retval -RED_ENOENT @p pszLocalPath is an empty string; or 204 * @p pszLocalPath does not name an existing file 205 * or directory. 206 * @retval -RED_ENOTDIR A component of the path other than the last is 207 * not a directory. 208 * @retval -RED_ENAMETOOLONG The length of a component of @p pszLocalPath is 209 * longer than #REDCONF_NAME_MAX. 210 */ RedPathLookup(const char * pszLocalPath,uint32_t * pulInode)211 REDSTATUS RedPathLookup( const char * pszLocalPath, 212 uint32_t * pulInode ) 213 { 214 REDSTATUS ret; 215 216 if( ( pszLocalPath == NULL ) || ( pulInode == NULL ) ) 217 { 218 REDERROR(); 219 ret = -RED_EINVAL; 220 } 221 else if( pszLocalPath[ 0U ] == '\0' ) 222 { 223 ret = -RED_ENOENT; 224 } 225 else if( IsRootDir( pszLocalPath ) ) 226 { 227 ret = 0; 228 *pulInode = INODE_ROOTDIR; 229 } 230 else 231 { 232 uint32_t ulPInode; 233 const char * pszName; 234 235 ret = RedPathToName( pszLocalPath, &ulPInode, &pszName ); 236 237 if( ret == 0 ) 238 { 239 ret = RedCoreLookup( ulPInode, pszName, pulInode ); 240 } 241 } 242 243 return ret; 244 } 245 246 247 /** @brief Given a path, return the parent inode number and a pointer to the 248 * last component in the path (the name). 249 * 250 * @param pszLocalPath The path to examine; this is a local path, without any 251 * volume prefix. 252 * @param pulPInode On successful return, populated with the inode number of 253 * the parent directory of the last component in the path. 254 * For example, with the path "a/b/c", populated with the 255 * inode number of "b". 256 * @param ppszName On successful return, populated with a pointer to the 257 * last component in the path. For example, with the path 258 * "a/b/c", populated with a pointer to "c". 259 * 260 * @return A negated ::REDSTATUS code indicating the operation result. 261 * 262 * @retval 0 Operation was successful. 263 * @retval -RED_EINVAL @p pszLocalPath is `NULL`; or @p pulPInode is 264 * `NULL`; or @p ppszName is `NULL`; or the path 265 * names the root directory. 266 * @retval -RED_EIO A disk I/O error occurred. 267 * @retval -RED_ENOENT @p pszLocalPath is an empty string; or a 268 * component of the path other than the last does 269 * not exist. 270 * @retval -RED_ENOTDIR A component of the path other than the last is 271 * not a directory. 272 * @retval -RED_ENAMETOOLONG The length of a component of @p pszLocalPath is 273 * longer than #REDCONF_NAME_MAX. 274 */ RedPathToName(const char * pszLocalPath,uint32_t * pulPInode,const char ** ppszName)275 REDSTATUS RedPathToName( const char * pszLocalPath, 276 uint32_t * pulPInode, 277 const char ** ppszName ) 278 { 279 REDSTATUS ret; 280 281 if( ( pszLocalPath == NULL ) || ( pulPInode == NULL ) || ( ppszName == NULL ) ) 282 { 283 REDERROR(); 284 ret = -RED_EINVAL; 285 } 286 else if( IsRootDir( pszLocalPath ) ) 287 { 288 ret = -RED_EINVAL; 289 } 290 else if( pszLocalPath[ 0U ] == '\0' ) 291 { 292 ret = -RED_ENOENT; 293 } 294 else 295 { 296 uint32_t ulInode = INODE_ROOTDIR; 297 uint32_t ulPInode = INODE_INVALID; 298 uint32_t ulPathIdx = 0U; 299 uint32_t ulLastNameIdx = 0U; 300 301 ret = 0; 302 303 do 304 { 305 uint32_t ulNameLen; 306 307 /* Skip over path separators, to get pszLocalPath[ulPathIdx] 308 * pointing at the next name. 309 */ 310 while( pszLocalPath[ ulPathIdx ] == REDCONF_PATH_SEPARATOR ) 311 { 312 ulPathIdx++; 313 } 314 315 if( pszLocalPath[ ulPathIdx ] == '\0' ) 316 { 317 break; 318 } 319 320 /* Point ulLastNameIdx at the first character of the name; after 321 * we exit the loop, it will point at the first character of the 322 * last name in the path. 323 */ 324 ulLastNameIdx = ulPathIdx; 325 326 /* Point ulPInode at the parent inode: either the root inode 327 * (first pass) or the inode of the previous name. After we exit 328 * the loop, this will point at the parent inode of the last name. 329 */ 330 ulPInode = ulInode; 331 332 ulNameLen = RedNameLen( &pszLocalPath[ ulPathIdx ] ); 333 334 /* Lookup the inode of the name, unless we are at the last name in 335 * the path: we don't care whether the last name exists or not. 336 */ 337 if( PathHasMoreNames( &pszLocalPath[ ulPathIdx + ulNameLen ] ) ) 338 { 339 ret = RedCoreLookup( ulPInode, &pszLocalPath[ ulPathIdx ], &ulInode ); 340 } 341 342 /* Move on to the next path element. 343 */ 344 if( ret == 0 ) 345 { 346 ulPathIdx += ulNameLen; 347 } 348 } 349 while( ret == 0 ); 350 351 if( ret == 0 ) 352 { 353 *pulPInode = ulPInode; 354 *ppszName = &pszLocalPath[ ulLastNameIdx ]; 355 } 356 } 357 358 return ret; 359 } 360 361 362 /** @brief Determine whether a path names the root directory. 363 * 364 * @param pszLocalPath The path to examine; this is a local path, without any 365 * volume prefix. 366 * 367 * @return Returns whether @p pszLocalPath names the root directory. 368 * 369 * @retval true @p pszLocalPath names the root directory. 370 * @retval false @p pszLocalPath does not name the root directory. 371 */ IsRootDir(const char * pszLocalPath)372 static bool IsRootDir( const char * pszLocalPath ) 373 { 374 bool fRet; 375 376 if( pszLocalPath == NULL ) 377 { 378 REDERROR(); 379 fRet = false; 380 } 381 else 382 { 383 uint32_t ulIdx = 0U; 384 385 /* A string containing nothing but path separators (usually only one) 386 * names the root directory. An empty string does *not* name the root 387 * directory, since in POSIX empty strings typically elicit -RED_ENOENT 388 * errors. 389 */ 390 while( pszLocalPath[ ulIdx ] == REDCONF_PATH_SEPARATOR ) 391 { 392 ulIdx++; 393 } 394 395 fRet = ( ulIdx > 0U ) && ( pszLocalPath[ ulIdx ] == '\0' ); 396 } 397 398 return fRet; 399 } 400 401 402 /** @brief Determine whether there are more names in a path. 403 * 404 * Example | Result 405 * ------- | ------ 406 * "" false 407 * "/" false 408 * "//" false 409 * "a" true 410 * "/a" true 411 * "//a" true 412 * 413 * @param pszPathIdx The path to examine, incremented to the point of 414 * interest. 415 * 416 * @return Returns whether there are more names in @p pszPathIdx. 417 * 418 * @retval true @p pszPathIdx has more names. 419 * @retval false @p pszPathIdx has no more names. 420 */ PathHasMoreNames(const char * pszPathIdx)421 static bool PathHasMoreNames( const char * pszPathIdx ) 422 { 423 bool fRet; 424 425 if( pszPathIdx == NULL ) 426 { 427 REDERROR(); 428 fRet = false; 429 } 430 else 431 { 432 uint32_t ulIdx = 0U; 433 434 while( pszPathIdx[ ulIdx ] == REDCONF_PATH_SEPARATOR ) 435 { 436 ulIdx++; 437 } 438 439 fRet = pszPathIdx[ ulIdx ] != '\0'; 440 } 441 442 return fRet; 443 } 444 445 #endif /* REDCONF_API_POSIX */ 446