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 directory operations. 29 */ 30 #include <redfs.h> 31 32 #if REDCONF_API_POSIX == 1 33 34 #include <redcore.h> 35 36 37 #define DIR_INDEX_INVALID UINT32_MAX 38 39 #if ( REDCONF_NAME_MAX % 4U ) != 0U 40 #define DIRENT_PADDING ( 4U - ( REDCONF_NAME_MAX % 4U ) ) 41 #else 42 #define DIRENT_PADDING ( 0U ) 43 #endif 44 #define DIRENT_SIZE ( 4U + REDCONF_NAME_MAX + DIRENT_PADDING ) 45 #define DIRENTS_PER_BLOCK ( REDCONF_BLOCK_SIZE / DIRENT_SIZE ) 46 #define DIRENTS_MAX ( uint32_t ) REDMIN( UINT32_MAX, UINT64_SUFFIX( 1 ) * INODE_DATA_BLOCKS * DIRENTS_PER_BLOCK ) 47 48 49 /** @brief On-disk directory entry. 50 */ 51 typedef struct 52 { 53 /** The inode number that the directory entry points at. If the directory 54 * entry is available, this holds INODE_INVALID. 55 */ 56 uint32_t ulInode; 57 58 /** The name of the directory entry. For names shorter than 59 * REDCONF_NAME_MAX, unused bytes in the array are zeroed. For names of 60 * the maximum length, the string is not null terminated. 61 */ 62 char acName[ REDCONF_NAME_MAX ]; 63 64 #if DIRENT_PADDING > 0U 65 66 /** Unused padding so that ulInode is always aligned on a four-byte 67 * boundary. 68 */ 69 uint8_t abPadding[ DIRENT_PADDING ]; 70 #endif 71 } DIRENT; 72 73 74 #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_RENAME == 1 ) 75 static REDSTATUS DirCyclicRenameCheck( uint32_t ulSrcInode, 76 const CINODE * pDstPInode ); 77 #endif 78 #if REDCONF_READ_ONLY == 0 79 static REDSTATUS DirEntryWrite( CINODE * pPInode, 80 uint32_t ulIdx, 81 uint32_t ulInode, 82 const char * pszName, 83 uint32_t ulNameLen ); 84 static uint64_t DirEntryIndexToOffset( uint32_t ulIdx ); 85 #endif 86 static uint32_t DirOffsetToEntryIndex( uint64_t ullOffset ); 87 88 89 #if REDCONF_READ_ONLY == 0 90 91 /** @brief Create a new entry in a directory. 92 * 93 * @param pPInode A pointer to the cached inode structure of the directory 94 * to which the new entry will be added. 95 * @param pszName The name to be given to the new entry, terminated by a 96 * null or a path separator. 97 * @param ulInode The inode number the new name will point at. 98 * 99 * @return A negated ::REDSTATUS code indicating the operation result. 100 * 101 * @retval 0 Operation was successful. 102 * @retval -RED_EIO A disk I/O error occurred. 103 * @retval -RED_ENOSPC There is not enough space on the volume to 104 * create the new directory entry; or the directory 105 * is full. 106 * @retval -RED_ENOTDIR @p pPInode is not a directory. 107 * @retval -RED_ENAMETOOLONG @p pszName is too long. 108 * @retval -RED_EEXIST @p pszName already exists in @p ulPInode. 109 * @retval -RED_EINVAL @p pPInode is not a mounted dirty cached inode 110 * structure; or @p pszName is not a valid name. 111 */ RedDirEntryCreate(CINODE * pPInode,const char * pszName,uint32_t ulInode)112 REDSTATUS RedDirEntryCreate( CINODE * pPInode, 113 const char * pszName, 114 uint32_t ulInode ) 115 { 116 REDSTATUS ret; 117 118 if( !CINODE_IS_DIRTY( pPInode ) || ( pszName == NULL ) || !INODE_IS_VALID( ulInode ) ) 119 { 120 ret = -RED_EINVAL; 121 } 122 else if( !pPInode->fDirectory ) 123 { 124 ret = -RED_ENOTDIR; 125 } 126 else 127 { 128 uint32_t ulNameLen = RedNameLen( pszName ); 129 130 if( ulNameLen == 0U ) 131 { 132 ret = -RED_EINVAL; 133 } 134 else if( ulNameLen > REDCONF_NAME_MAX ) 135 { 136 ret = -RED_ENAMETOOLONG; 137 } 138 else 139 { 140 uint32_t ulEntryIdx; 141 142 ret = RedDirEntryLookup( pPInode, pszName, &ulEntryIdx, NULL ); 143 144 if( ret == 0 ) 145 { 146 ret = -RED_EEXIST; 147 } 148 else if( ret == -RED_ENOENT ) 149 { 150 if( ulEntryIdx == DIR_INDEX_INVALID ) 151 { 152 ret = -RED_ENOSPC; 153 } 154 else 155 { 156 ret = 0; 157 } 158 } 159 else 160 { 161 /* Unexpected error, no action. 162 */ 163 } 164 165 if( ret == 0 ) 166 { 167 ret = DirEntryWrite( pPInode, ulEntryIdx, ulInode, pszName, ulNameLen ); 168 } 169 } 170 } 171 172 return ret; 173 } 174 #endif /* REDCONF_READ_ONLY == 0 */ 175 176 177 #if DELETE_SUPPORTED 178 179 /** @brief Delete an existing directory entry. 180 * 181 * @param pPInode A pointer to the cached inode structure of the directory 182 * containing the entry to be deleted. 183 * @param ulDeleteIdx Position within the directory of the entry to be 184 * deleted. 185 * 186 * @return A negated ::REDSTATUS code indicating the operation result. 187 * 188 * @retval 0 Operation was successful. 189 * @retval -RED_EIO A disk I/O error occurred. 190 * @retval -RED_ENOSPC The file system does not have enough space to modify 191 * the parent directory to perform the deletion. 192 * @retval -RED_ENOTDIR @p pPInode is not a directory. 193 * @retval -RED_EINVAL @p pPInode is not a mounted dirty cached inode 194 * structure; or @p ulIdx is out of range. 195 */ RedDirEntryDelete(CINODE * pPInode,uint32_t ulDeleteIdx)196 REDSTATUS RedDirEntryDelete( CINODE * pPInode, 197 uint32_t ulDeleteIdx ) 198 { 199 REDSTATUS ret = 0; 200 201 if( !CINODE_IS_DIRTY( pPInode ) || ( ulDeleteIdx >= DIRENTS_MAX ) ) 202 { 203 ret = -RED_EINVAL; 204 } 205 else if( !pPInode->fDirectory ) 206 { 207 ret = -RED_ENOTDIR; 208 } 209 else if( ( DirEntryIndexToOffset( ulDeleteIdx ) + DIRENT_SIZE ) == pPInode->pInodeBuf->ullSize ) 210 { 211 /* Start searching one behind the index to be deleted. 212 */ 213 uint32_t ulTruncIdx = ulDeleteIdx - 1U; 214 bool fDone = false; 215 216 /* We are deleting the last dirent in the directory, so search 217 * backwards to find the last populated dirent, allowing us to truncate 218 * the directory to that point. 219 */ 220 while( ( ret == 0 ) && ( ulTruncIdx != UINT32_MAX ) && !fDone ) 221 { 222 ret = RedInodeDataSeekAndRead( pPInode, ulTruncIdx / DIRENTS_PER_BLOCK ); 223 224 if( ret == 0 ) 225 { 226 const DIRENT * pDirents = CAST_CONST_DIRENT_PTR( pPInode->pbData ); 227 uint32_t ulBlockIdx = ulTruncIdx % DIRENTS_PER_BLOCK; 228 229 do 230 { 231 if( pDirents[ ulBlockIdx ].ulInode != INODE_INVALID ) 232 { 233 fDone = true; 234 break; 235 } 236 237 ulTruncIdx--; 238 ulBlockIdx--; 239 } while( ulBlockIdx != UINT32_MAX ); 240 } 241 else if( ret == -RED_ENODATA ) 242 { 243 ret = 0; 244 245 REDASSERT( ( ulTruncIdx % DIRENTS_PER_BLOCK ) == 0U ); 246 ulTruncIdx -= DIRENTS_PER_BLOCK; 247 } 248 else 249 { 250 /* Unexpected error, loop will terminate; nothing else 251 * to be done. 252 */ 253 } 254 } 255 256 /* Currently ulTruncIdx represents the last valid dirent index, or 257 * UINT32_MAX if the directory is now empty. Increment it so that it 258 * represents the first invalid entry, which will be truncated. 259 */ 260 ulTruncIdx++; 261 262 /* Truncate the directory, deleting the requested entry and any empty 263 * dirents at the end of the directory. 264 */ 265 if( ret == 0 ) 266 { 267 ret = RedInodeDataTruncate( pPInode, DirEntryIndexToOffset( ulTruncIdx ) ); 268 } 269 } 270 else 271 { 272 /* The dirent to delete is not the last entry in the directory, so just 273 * zero it. 274 */ 275 ret = DirEntryWrite( pPInode, ulDeleteIdx, INODE_INVALID, "", 0U ); 276 } 277 278 return ret; 279 } 280 #endif /* DELETE_SUPPORTED */ 281 282 283 /** @brief Perform a case-sensitive search of a directory for a given name. 284 * 285 * If found, then position of the entry within the directory and the inode 286 * number it points to are returned. As an optimization for directory entry 287 * creation, in the case where the requested entry does not exist, the position 288 * of the first available (unused) entry is returned. 289 * 290 * @param pPInode A pointer to the cached inode structure of the directory 291 * to search. 292 * @param pszName The name of the desired entry, terminated by either a 293 * null or a path separator. 294 * @param pulEntryIdx On successful return, meaning that the desired entry 295 * exists, populated with the position of the entry. If 296 * returning an -RED_ENOENT error, populated with the 297 * position of the first available entry, or set to 298 * DIR_INDEX_INVALID if the directory is full. Optional. 299 * @param pulInode On successful return, populated with the inode number 300 * that the name points to. Optional; may be `NULL`. 301 * 302 * @return A negated ::REDSTATUS code indicating the operation result. 303 * 304 * @retval 0 Operation was successful. 305 * @retval -RED_EIO A disk I/O error occurred. 306 * @retval -RED_ENOENT @p pszName does not name an existing file or 307 * directory. 308 * @retval -RED_ENOTDIR @p pPInode is not a directory. 309 * @retval -RED_EINVAL @p pPInode is not a mounted cached inode 310 * structure; or @p pszName is not a valid name; or 311 * @p pulEntryIdx is `NULL`. 312 * @retval -RED_ENAMETOOLONG @p pszName is too long. 313 */ RedDirEntryLookup(CINODE * pPInode,const char * pszName,uint32_t * pulEntryIdx,uint32_t * pulInode)314 REDSTATUS RedDirEntryLookup( CINODE * pPInode, 315 const char * pszName, 316 uint32_t * pulEntryIdx, 317 uint32_t * pulInode ) 318 { 319 REDSTATUS ret = 0; 320 321 if( !CINODE_IS_MOUNTED( pPInode ) || ( pszName == NULL ) ) 322 { 323 ret = -RED_EINVAL; 324 } 325 else if( !pPInode->fDirectory ) 326 { 327 ret = -RED_ENOTDIR; 328 } 329 else 330 { 331 uint32_t ulNameLen = RedNameLen( pszName ); 332 333 if( ulNameLen == 0U ) 334 { 335 ret = -RED_EINVAL; 336 } 337 else if( ulNameLen > REDCONF_NAME_MAX ) 338 { 339 ret = -RED_ENAMETOOLONG; 340 } 341 else 342 { 343 uint32_t ulIdx = 0U; 344 uint32_t ulDirentCount = DirOffsetToEntryIndex( pPInode->pInodeBuf->ullSize ); 345 uint32_t ulFreeIdx = DIR_INDEX_INVALID; /* Index of first free dirent. */ 346 347 /* Loop over the directory blocks, searching each block for a 348 * dirent that matches the given name. 349 */ 350 while( ( ret == 0 ) && ( ulIdx < ulDirentCount ) ) 351 { 352 ret = RedInodeDataSeekAndRead( pPInode, ulIdx / DIRENTS_PER_BLOCK ); 353 354 if( ret == 0 ) 355 { 356 const DIRENT * pDirents = CAST_CONST_DIRENT_PTR( pPInode->pbData ); 357 uint32_t ulBlockLastIdx = REDMIN( DIRENTS_PER_BLOCK, ulDirentCount - ulIdx ); 358 uint32_t ulBlockIdx; 359 360 for( ulBlockIdx = 0U; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++ ) 361 { 362 const DIRENT * pDirent = &pDirents[ ulBlockIdx ]; 363 364 if( pDirent->ulInode != INODE_INVALID ) 365 { 366 /* The name in the dirent will not be null 367 * terminated if it is of the maximum length, so 368 * use a bounded string compare and then make sure 369 * there is nothing more to the name. 370 */ 371 if( ( RedStrNCmp( pDirent->acName, pszName, ulNameLen ) == 0 ) && 372 ( ( ulNameLen == REDCONF_NAME_MAX ) || ( pDirent->acName[ ulNameLen ] == '\0' ) ) ) 373 { 374 /* Found a matching dirent, stop and return its 375 * information. 376 */ 377 if( pulInode != NULL ) 378 { 379 *pulInode = pDirent->ulInode; 380 381 #ifdef REDCONF_ENDIAN_SWAP 382 *pulInode = RedRev32( *pulInode ); 383 #endif 384 } 385 386 ulIdx += ulBlockIdx; 387 break; 388 } 389 } 390 else if( ulFreeIdx == DIR_INDEX_INVALID ) 391 { 392 ulFreeIdx = ulIdx + ulBlockIdx; 393 } 394 else 395 { 396 /* The directory entry is free, but we already found a free one, so there's 397 * nothing to do here. 398 */ 399 } 400 } 401 402 if( ulBlockIdx < ulBlockLastIdx ) 403 { 404 /* If we broke out of the for loop, we found a matching 405 * dirent and can stop the search. 406 */ 407 break; 408 } 409 410 ulIdx += ulBlockLastIdx; 411 } 412 else if( ret == -RED_ENODATA ) 413 { 414 if( ulFreeIdx == DIR_INDEX_INVALID ) 415 { 416 ulFreeIdx = ulIdx; 417 } 418 419 ret = 0; 420 ulIdx += DIRENTS_PER_BLOCK; 421 } 422 else 423 { 424 /* Unexpected error, let the loop terminate, no action 425 * here. 426 */ 427 } 428 } 429 430 if( ret == 0 ) 431 { 432 /* If we made it all the way to the end of the directory 433 * without stopping, then the given name does not exist in the 434 * directory. 435 */ 436 if( ulIdx == ulDirentCount ) 437 { 438 /* If the directory had no sparse dirents, then the first 439 * free dirent is beyond the end of the directory. If the 440 * directory is already the maximum size, then there is no 441 * free dirent. 442 */ 443 if( ( ulFreeIdx == DIR_INDEX_INVALID ) && ( ulDirentCount < DIRENTS_MAX ) ) 444 { 445 ulFreeIdx = ulDirentCount; 446 } 447 448 ulIdx = ulFreeIdx; 449 450 ret = -RED_ENOENT; 451 } 452 453 if( pulEntryIdx != NULL ) 454 { 455 *pulEntryIdx = ulIdx; 456 } 457 } 458 } 459 } 460 461 return ret; 462 } 463 464 465 #if ( REDCONF_API_POSIX_READDIR == 1 ) || ( REDCONF_CHECKER == 1 ) 466 467 /** @brief Read the next entry from a directory, given a starting index. 468 * 469 * @param pPInode A pointer to the cached inode structure of the directory to 470 * read from. 471 * @param pulIdx On entry, the directory index to start reading from. On 472 * successful return, populated with the directory index to use 473 * for subsequent reads. On -RED_ENOENT return, populated with 474 * the directory index immediately following the last valid 475 * one. 476 * @param pszName On successful return, populated with the name of the next 477 * directory entry. Buffer must be at least 478 * REDCONF_NAME_MAX + 1 in size, to store the maximum name 479 * length plus a null terminator. 480 * @param pulInode On successful return, populated with the inode number 481 * pointed at by the next directory entry. 482 * 483 * @return A negated ::REDSTATUS code indicating the operation result. 484 * 485 * @retval 0 Operation was successful. 486 * @retval -RED_EIO A disk I/O error occurred. 487 * @retval -RED_ENOENT There are no more entries in the directory. 488 * @retval -RED_ENOTDIR @p pPInode is not a directory. 489 * @retval -RED_EINVAL @p pPInode is not a mounted cached inode structure; 490 * or @p pszName is `NULL`; or @p pulIdx is `NULL`; or 491 * @p pulInode is `NULL`. 492 */ RedDirEntryRead(CINODE * pPInode,uint32_t * pulIdx,char * pszName,uint32_t * pulInode)493 REDSTATUS RedDirEntryRead( CINODE * pPInode, 494 uint32_t * pulIdx, 495 char * pszName, 496 uint32_t * pulInode ) 497 { 498 REDSTATUS ret = 0; 499 500 if( !CINODE_IS_MOUNTED( pPInode ) || ( pulIdx == NULL ) || ( pszName == NULL ) || ( pulInode == NULL ) ) 501 { 502 ret = -RED_EINVAL; 503 } 504 else if( !pPInode->fDirectory ) 505 { 506 ret = -RED_ENOTDIR; 507 } 508 else 509 { 510 uint32_t ulIdx = *pulIdx; 511 uint32_t ulDirentCount = DirOffsetToEntryIndex( pPInode->pInodeBuf->ullSize ); 512 513 /* Starting either at the beginning of the directory or where we left 514 * off, loop over the directory blocks, searching each block for a 515 * non-sparse dirent to return as the next entry in the directory. 516 */ 517 while( ( ret == 0 ) && ( ulIdx < ulDirentCount ) ) 518 { 519 uint32_t ulBlockOffset = ulIdx / DIRENTS_PER_BLOCK; 520 521 ret = RedInodeDataSeekAndRead( pPInode, ulBlockOffset ); 522 523 if( ret == 0 ) 524 { 525 const DIRENT * pDirents = CAST_CONST_DIRENT_PTR( pPInode->pbData ); 526 uint32_t ulBlockLastIdx = REDMIN( DIRENTS_PER_BLOCK, ulDirentCount - ( ulBlockOffset * DIRENTS_PER_BLOCK ) ); 527 uint32_t ulBlockIdx; 528 529 for( ulBlockIdx = ulIdx % DIRENTS_PER_BLOCK; ulBlockIdx < ulBlockLastIdx; ulBlockIdx++ ) 530 { 531 if( pDirents[ ulBlockIdx ].ulInode != INODE_INVALID ) 532 { 533 *pulIdx = ulIdx + 1U; 534 RedStrNCpy( pszName, pDirents[ ulBlockIdx ].acName, REDCONF_NAME_MAX ); 535 pszName[ REDCONF_NAME_MAX ] = '\0'; 536 537 *pulInode = pDirents[ ulBlockIdx ].ulInode; 538 539 #ifdef REDCONF_ENDIAN_SWAP 540 *pulInode = RedRev32( *pulInode ); 541 #endif 542 break; 543 } 544 545 ulIdx++; 546 } 547 548 if( ulBlockIdx < ulBlockLastIdx ) 549 { 550 REDASSERT( ulIdx <= ulDirentCount ); 551 break; 552 } 553 } 554 else if( ret == -RED_ENODATA ) 555 { 556 ulIdx += DIRENTS_PER_BLOCK - ( ulIdx % DIRENTS_PER_BLOCK ); 557 ret = 0; 558 } 559 else 560 { 561 /* Unexpected error, loop will terminate; nothing else to do. 562 */ 563 } 564 565 REDASSERT( ulIdx <= ulDirentCount ); 566 } 567 568 if( ( ret == 0 ) && ( ulIdx >= ulDirentCount ) ) 569 { 570 *pulIdx = ulDirentCount; 571 ret = -RED_ENOENT; 572 } 573 } 574 575 return ret; 576 } 577 #endif /* if ( REDCONF_API_POSIX_READDIR == 1 ) || ( REDCONF_CHECKER == 1 ) */ 578 579 580 #if ( REDCONF_READ_ONLY == 0 ) && ( REDCONF_API_POSIX_RENAME == 1 ) 581 582 /** Rename a directory entry. 583 * 584 * @param pSrcPInode The inode of the directory containing @p pszSrcName. 585 * @param pszSrcName The name of the directory entry to be renamed. 586 * @param pSrcInode On successful return, populated with the inode of the 587 * source entry. 588 * @param pDstPInode The inode of the directory in which @p pszDstName will 589 * be created or replaced. 590 * @param pszDstName The name of the directory entry to be created or 591 * replaced. 592 * @param pDstInode On successful return, if the destination previously 593 * existed, populated with the inode previously pointed to 594 * by the destination. This may be the same as the source 595 * inode. If the destination did not exist, populated with 596 * INODE_INVALID. 597 * 598 * @return A negated ::REDSTATUS code indicating the operation result. 599 * 600 * @retval 0 Operation was successful. 601 * @retval -RED_EEXIST #REDCONF_RENAME_ATOMIC is false and the 602 * destination name exists. 603 * @retval -RED_EINVAL @p pSrcPInode is not a mounted dirty cached 604 * inode structure; or @p pSrcInode is `NULL`; or 605 * @p pszSrcName is not a valid name; or 606 * @p pDstPInode is not a mounted dirty cached 607 * inode structure; or @p pDstInode is `NULL`; or 608 * @p pszDstName is not a valid name. 609 * @retval -RED_EIO A disk I/O error occurred. 610 * @retval -RED_EISDIR The destination name exists and is a directory, 611 * and the source name is a non-directory. 612 * @retval -RED_ENAMETOOLONG Either @p pszSrcName or @p pszDstName is longer 613 * than #REDCONF_NAME_MAX. 614 * @retval -RED_ENOENT The source name is not an existing entry; or 615 * either @p pszSrcName or @p pszDstName point to 616 * an empty string. 617 * @retval -RED_ENOTDIR @p pSrcPInode is not a directory; or 618 * @p pDstPInode is not a directory; or the source 619 * name is a directory and the destination name is 620 * a file. 621 * @retval -RED_ENOTEMPTY The destination name is a directory which is not 622 * empty. 623 * @retval -RED_ENOSPC The file system does not have enough space to 624 * extend the @p ulDstPInode directory. 625 * @retval -RED_EROFS The directory to be removed resides on a 626 * read-only file system. 627 */ RedDirEntryRename(CINODE * pSrcPInode,const char * pszSrcName,CINODE * pSrcInode,CINODE * pDstPInode,const char * pszDstName,CINODE * pDstInode)628 REDSTATUS RedDirEntryRename( CINODE * pSrcPInode, 629 const char * pszSrcName, 630 CINODE * pSrcInode, 631 CINODE * pDstPInode, 632 const char * pszDstName, 633 CINODE * pDstInode ) 634 { 635 REDSTATUS ret; 636 637 if( !CINODE_IS_DIRTY( pSrcPInode ) || 638 ( pszSrcName == NULL ) || 639 ( pSrcInode == NULL ) || 640 !CINODE_IS_DIRTY( pDstPInode ) || 641 ( pszDstName == NULL ) || 642 ( pDstInode == NULL ) ) 643 { 644 ret = -RED_EINVAL; 645 } 646 else if( !pSrcPInode->fDirectory || !pDstPInode->fDirectory ) 647 { 648 ret = -RED_ENOTDIR; 649 } 650 else 651 { 652 uint32_t ulDstIdx = 0U; /* Init'd to quiet warnings. */ 653 uint32_t ulSrcIdx; 654 655 /* Look up the source and destination names. 656 */ 657 ret = RedDirEntryLookup( pSrcPInode, pszSrcName, &ulSrcIdx, &pSrcInode->ulInode ); 658 659 if( ret == 0 ) 660 { 661 ret = RedDirEntryLookup( pDstPInode, pszDstName, &ulDstIdx, &pDstInode->ulInode ); 662 663 if( ret == -RED_ENOENT ) 664 { 665 if( ulDstIdx == DIR_INDEX_INVALID ) 666 { 667 ret = -RED_ENOSPC; 668 } 669 else 670 { 671 #if REDCONF_RENAME_ATOMIC == 1 672 pDstInode->ulInode = INODE_INVALID; 673 #endif 674 ret = 0; 675 } 676 } 677 678 #if REDCONF_RENAME_ATOMIC == 0 679 else if( ret == 0 ) 680 { 681 ret = -RED_EEXIST; 682 } 683 else 684 { 685 /* Nothing to do here, just propagate the error. 686 */ 687 } 688 #endif 689 } 690 691 #if REDCONF_RENAME_ATOMIC == 1 692 693 /* If both names point to the same inode, POSIX says to do nothing to 694 * either name. 695 */ 696 if( ( ret == 0 ) && ( pSrcInode->ulInode != pDstInode->ulInode ) ) 697 #else 698 if( ret == 0 ) 699 #endif 700 { 701 ret = RedInodeMount( pSrcInode, FTYPE_EITHER, true ); 702 703 #if REDCONF_RENAME_ATOMIC == 1 704 if( ( ret == 0 ) && ( pDstInode->ulInode != INODE_INVALID ) ) 705 { 706 /* Source and destination must be the same type (file/dir). 707 */ 708 ret = RedInodeMount( pDstInode, pSrcInode->fDirectory ? FTYPE_DIR : FTYPE_FILE, true ); 709 710 /* If renaming directories, the destination must be empty. 711 */ 712 if( ( ret == 0 ) && pDstInode->fDirectory && ( pDstInode->pInodeBuf->ullSize > 0U ) ) 713 { 714 ret = -RED_ENOTEMPTY; 715 } 716 } 717 #endif /* if REDCONF_RENAME_ATOMIC == 1 */ 718 719 /* If we are renaming a directory, make sure the rename isn't 720 * cyclic (e.g., renaming "foo" into "foo/bar"). 721 */ 722 if( ( ret == 0 ) && pSrcInode->fDirectory ) 723 { 724 ret = DirCyclicRenameCheck( pSrcInode->ulInode, pDstPInode ); 725 } 726 727 if( ret == 0 ) 728 { 729 ret = DirEntryWrite( pDstPInode, ulDstIdx, pSrcInode->ulInode, pszDstName, RedNameLen( pszDstName ) ); 730 } 731 732 if( ret == 0 ) 733 { 734 ret = RedDirEntryDelete( pSrcPInode, ulSrcIdx ); 735 736 if( ret == -RED_ENOSPC ) 737 { 738 REDSTATUS ret2; 739 740 /* If there was not enough space to branch the parent 741 * directory inode and data block containing the source 742 * entry, revert destination directory entry to its 743 * previous state. 744 */ 745 #if REDCONF_RENAME_ATOMIC == 1 746 if( pDstInode->ulInode != INODE_INVALID ) 747 { 748 ret2 = DirEntryWrite( pDstPInode, ulDstIdx, pDstInode->ulInode, pszDstName, RedNameLen( pszDstName ) ); 749 } 750 else 751 #endif 752 { 753 ret2 = RedDirEntryDelete( pDstPInode, ulDstIdx ); 754 } 755 756 if( ret2 != 0 ) 757 { 758 ret = ret2; 759 CRITICAL_ERROR(); 760 } 761 } 762 } 763 764 if( ret == 0 ) 765 { 766 pSrcInode->pInodeBuf->ulPInode = pDstPInode->ulInode; 767 } 768 } 769 } 770 771 return ret; 772 } 773 774 775 /** @brief Check for a cyclic rename. 776 * 777 * A cyclic rename is renaming a directory into a subdirectory of itself. For 778 * example, renaming "a" into "a/b/c/d" is cyclic. These renames must not be 779 * allowed since they would corrupt the directory tree. 780 * 781 * @param ulSrcInode The inode number of the directory being renamed. 782 * @param pDstPInode A pointer to the cached inode structure of the directory 783 * into which the source is being renamed. 784 * 785 * @return A negated ::REDSTATUS code indicating the operation result. 786 * 787 * @retval 0 Operation was successful. 788 * @retval -RED_EIO A disk I/O error occurred. 789 * @retval -RED_EINVAL The rename is cyclic; or invalid parameters. 790 * @retval -RED_ENOTDIR @p pDstPInode is not a directory. 791 */ DirCyclicRenameCheck(uint32_t ulSrcInode,const CINODE * pDstPInode)792 static REDSTATUS DirCyclicRenameCheck( uint32_t ulSrcInode, 793 const CINODE * pDstPInode ) 794 { 795 REDSTATUS ret = 0; 796 797 if( !INODE_IS_VALID( ulSrcInode ) || !CINODE_IS_MOUNTED( pDstPInode ) ) 798 { 799 REDERROR(); 800 ret = -RED_EINVAL; 801 } 802 else if( ulSrcInode == pDstPInode->ulInode ) 803 { 804 ret = -RED_EINVAL; 805 } 806 else if( !pDstPInode->fDirectory ) 807 { 808 ret = -RED_ENOTDIR; 809 } 810 else 811 { 812 CINODE NextParent; 813 814 /* Used to prevent infinite loop in case of corrupted directory 815 * structure. 816 */ 817 uint32_t ulIteration = 0U; 818 819 NextParent.ulInode = pDstPInode->pInodeBuf->ulPInode; 820 821 while( ( NextParent.ulInode != ulSrcInode ) && 822 ( NextParent.ulInode != INODE_ROOTDIR ) && 823 ( NextParent.ulInode != INODE_INVALID ) && 824 ( ulIteration < gpRedVolConf->ulInodeCount ) ) 825 { 826 ret = RedInodeMount( &NextParent, FTYPE_DIR, false ); 827 828 if( ret != 0 ) 829 { 830 break; 831 } 832 833 NextParent.ulInode = NextParent.pInodeBuf->ulPInode; 834 835 RedInodePut( &NextParent, 0U ); 836 837 ulIteration++; 838 } 839 840 if( ( ret == 0 ) && ( ulIteration == gpRedVolConf->ulInodeCount ) ) 841 { 842 CRITICAL_ERROR(); 843 ret = -RED_EFUBAR; 844 } 845 846 if( ( ret == 0 ) && ( ulSrcInode == NextParent.ulInode ) ) 847 { 848 ret = -RED_EINVAL; 849 } 850 } 851 852 return ret; 853 } 854 #endif /* (REDCONF_READ_ONLY == 0) && (REDCONF_API_POSIX_RENAME == 1) */ 855 856 857 #if REDCONF_READ_ONLY == 0 858 859 /** @brief Update the contents of a directory entry. 860 * 861 * @param pPInode A pointer to the cached inode structure of the directory 862 * whose entry is being written. 863 * @param ulIdx The index of the directory entry to write. 864 * @param ulInode The inode number the directory entry is to point at. 865 * @param pszName The name of the directory entry. 866 * @param ulNameLen The length of @p pszName. 867 * 868 * @return A negated ::REDSTATUS code indicating the operation result. 869 * 870 * @retval 0 Operation was successful. 871 * @retval -RED_EIO A disk I/O error occurred. 872 * @retval -RED_ENOSPC There is not enough space on the volume to write the 873 * directory entry. 874 * @retval -RED_ENOTDIR @p pPInode is not a directory. 875 * @retval -RED_EINVAL Invalid parameters. 876 */ DirEntryWrite(CINODE * pPInode,uint32_t ulIdx,uint32_t ulInode,const char * pszName,uint32_t ulNameLen)877 static REDSTATUS DirEntryWrite( CINODE * pPInode, 878 uint32_t ulIdx, 879 uint32_t ulInode, 880 const char * pszName, 881 uint32_t ulNameLen ) 882 { 883 REDSTATUS ret; 884 885 if( !CINODE_IS_DIRTY( pPInode ) || 886 ( ulIdx >= DIRENTS_MAX ) || 887 ( !INODE_IS_VALID( ulInode ) && ( ulInode != INODE_INVALID ) ) || 888 ( pszName == NULL ) || 889 ( ulNameLen > REDCONF_NAME_MAX ) || 890 ( ( ulNameLen == 0U ) != ( ulInode == INODE_INVALID ) ) ) 891 { 892 REDERROR(); 893 ret = -RED_EINVAL; 894 } 895 else if( !pPInode->fDirectory ) 896 { 897 ret = -RED_ENOTDIR; 898 } 899 else 900 { 901 uint64_t ullOffset = DirEntryIndexToOffset( ulIdx ); 902 uint32_t ulLen = DIRENT_SIZE; 903 static DIRENT de; 904 905 RedMemSet( &de, 0U, sizeof( de ) ); 906 907 de.ulInode = ulInode; 908 909 #ifdef REDCONF_ENDIAN_SWAP 910 de.ulInode = RedRev32( de.ulInode ); 911 #endif 912 913 RedStrNCpy( de.acName, pszName, ulNameLen ); 914 915 ret = RedInodeDataWrite( pPInode, ullOffset, &ulLen, &de ); 916 } 917 918 return ret; 919 } 920 921 922 /** @brief Convert a directory entry index to a byte offset. 923 * 924 * @param ulIdx Directory entry index. 925 * 926 * @return Byte offset in the directory corresponding with ulIdx. 927 */ DirEntryIndexToOffset(uint32_t ulIdx)928 static uint64_t DirEntryIndexToOffset( uint32_t ulIdx ) 929 { 930 uint32_t ulBlock = ulIdx / DIRENTS_PER_BLOCK; 931 uint32_t ulOffsetInBlock = ulIdx % DIRENTS_PER_BLOCK; 932 uint64_t ullOffset; 933 934 REDASSERT( ulIdx < DIRENTS_MAX ); 935 936 ullOffset = ( uint64_t ) ulBlock << BLOCK_SIZE_P2; 937 ullOffset += ( uint64_t ) ulOffsetInBlock * DIRENT_SIZE; 938 939 return ullOffset; 940 } 941 #endif /* REDCONF_READ_ONLY == 0 */ 942 943 944 /** @brief Convert a byte offset to a directory entry index. 945 * 946 * @param ullOffset Byte offset in the directory. 947 * 948 * @return Directory entry index corresponding with @p ullOffset. 949 */ DirOffsetToEntryIndex(uint64_t ullOffset)950 static uint32_t DirOffsetToEntryIndex( uint64_t ullOffset ) 951 { 952 uint32_t ulIdx; 953 954 REDASSERT( ullOffset < INODE_SIZE_MAX ); 955 REDASSERT( ( ( uint32_t ) ( ullOffset & ( REDCONF_BLOCK_SIZE - 1U ) ) % DIRENT_SIZE ) == 0U ); 956 957 /* Avoid doing any 64-bit divides. 958 */ 959 ulIdx = ( uint32_t ) ( ullOffset >> BLOCK_SIZE_P2 ) * DIRENTS_PER_BLOCK; 960 ulIdx += ( uint32_t ) ( ullOffset & ( REDCONF_BLOCK_SIZE - 1U ) ) / DIRENT_SIZE; 961 962 return ulIdx; 963 } 964 965 966 #endif /* REDCONF_API_POSIX == 1 */ 967