1 /* 2 * Copyright 2002-2012, Haiku Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Tyler Dauwalder 7 * Ingo Weinhold, bonefish@users.sf.net 8 */ 9 10 11 /*! 12 \file Path.cpp 13 BPath implementation. 14 */ 15 16 17 #include <Path.h> 18 19 #include <new> 20 21 #include <Directory.h> 22 #include <Entry.h> 23 #include <StorageDefs.h> 24 #include <String.h> 25 26 #include <syscalls.h> 27 28 #include "storage_support.h" 29 30 using namespace std; 31 32 33 //! Creates an uninitialized BPath object. 34 BPath::BPath() 35 : 36 fName(NULL), 37 fCStatus(B_NO_INIT) 38 { 39 } 40 41 42 /*! Creates a copy of the given BPath object. 43 \param path the object to be copied 44 */ 45 BPath::BPath(const BPath& path) 46 : 47 fName(NULL), 48 fCStatus(B_NO_INIT) 49 { 50 *this = path; 51 } 52 53 54 /*! \brief Creates a BPath object and initializes it to the filesystem entry 55 specified by the given entry_ref struct. 56 \param ref the entry_ref 57 */ 58 BPath::BPath(const entry_ref* ref) 59 : 60 fName(NULL), 61 fCStatus(B_NO_INIT) 62 { 63 SetTo(ref); 64 } 65 66 67 /*! \brief Creates a BPath object and initializes it to the filesystem entry 68 specified by the given BEntry object. 69 \param entry the BEntry object 70 */ 71 BPath::BPath(const BEntry* entry) 72 : 73 fName(NULL), 74 fCStatus(B_NO_INIT) 75 { 76 SetTo(entry); 77 } 78 79 80 /*! \brief Creates a BPath object and initializes it to the specified path or 81 path and filename combination. 82 83 \param dir The base component of the pathname. May be absolute or relative. 84 If relative, it is reckoned off the current working directory. 85 \param leaf The (optional) leaf component of the pathname. Must be 86 relative. The value of leaf is concatenated to the end of \a dir 87 (a "/" will be added as a separator, if necessary). 88 \param normalize boolean flag used to force normalization; normalization 89 may occur even if false (see \ref _MustNormalize). 90 */ 91 BPath::BPath(const char* dir, const char* leaf, bool normalize) 92 : 93 fName(NULL), 94 fCStatus(B_NO_INIT) 95 { 96 SetTo(dir, leaf, normalize); 97 } 98 99 100 /*! \brief Creates a BPath object and initializes it to the specified directory 101 and filename combination. 102 \param dir Refers to the directory that provides the base component of the 103 pathname. 104 \param leaf The (optional) leaf component of the pathname. Must be 105 relative. The value of leaf is concatenated to the end of \a dir 106 (a "/" will be added as a separator, if necessary). 107 \param normalize boolean flag used to force normalization; normalization 108 may occur even if false (see \ref _MustNormalize). 109 */ 110 BPath::BPath(const BDirectory* dir, const char* leaf, bool normalize) 111 : 112 fName(NULL), 113 fCStatus(B_NO_INIT) 114 { 115 SetTo(dir, leaf, normalize); 116 } 117 118 119 //! Destroys the BPath object and frees any of its associated resources. 120 BPath::~BPath() 121 { 122 Unset(); 123 } 124 125 126 /*! \brief Returns the status of the most recent construction or SetTo() call. 127 \return \c B_OK, if the BPath object is properly initialized, an error 128 code otherwise. 129 */ 130 status_t 131 BPath::InitCheck() const 132 { 133 return fCStatus; 134 } 135 136 137 /*! \brief Reinitializes the object to the filesystem entry specified by the 138 given entry_ref struct. 139 \param ref the entry_ref 140 \return 141 - \c B_OK: The initialization was successful. 142 - \c B_BAD_VALUE: \c NULL \a ref. 143 - \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH. 144 - other error codes. 145 */ 146 status_t 147 BPath::SetTo(const entry_ref* ref) 148 { 149 Unset(); 150 if (!ref) 151 return fCStatus = B_BAD_VALUE; 152 153 char path[B_PATH_NAME_LENGTH]; 154 fCStatus = _kern_entry_ref_to_path(ref->device, ref->directory, 155 ref->name, path, sizeof(path)); 156 if (fCStatus != B_OK) 157 return fCStatus; 158 159 fCStatus = _SetPath(path); 160 // the path is already normalized 161 return fCStatus; 162 } 163 164 165 /*! \brief Reinitializes the object to the specified filesystem entry. 166 \param entry the BEntry 167 \return 168 - \c B_OK: The initialization was successful. 169 - \c B_BAD_VALUE: \c NULL \a entry. 170 - \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH. 171 - other error codes. 172 */ 173 status_t 174 BPath::SetTo(const BEntry* entry) 175 { 176 Unset(); 177 if (entry == NULL) 178 return B_BAD_VALUE; 179 180 entry_ref ref; 181 fCStatus = entry->GetRef(&ref); 182 if (fCStatus == B_OK) 183 fCStatus = SetTo(&ref); 184 185 return fCStatus; 186 } 187 188 189 /*! \brief Reinitializes the object to the specified path or path and file 190 name combination. 191 \param path the path name 192 \param leaf the leaf name (may be \c NULL) 193 \param normalize boolean flag used to force normalization; normalization 194 may occur even if false (see \ref _MustNormalize). 195 \return 196 - \c B_OK: The initialization was successful. 197 - \c B_BAD_VALUE: \c NULL \a path or absolute \a leaf. 198 - \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH. 199 - other error codes. 200 \note \code path.SetTo(path.Path(), "new leaf") \endcode is safe. 201 */ 202 status_t 203 BPath::SetTo(const char* path, const char* leaf, bool normalize) 204 { 205 status_t error = (path ? B_OK : B_BAD_VALUE); 206 if (error == B_OK && leaf && BPrivate::Storage::is_absolute_path(leaf)) 207 error = B_BAD_VALUE; 208 char newPath[B_PATH_NAME_LENGTH]; 209 if (error == B_OK) { 210 // we always normalize relative paths 211 normalize |= !BPrivate::Storage::is_absolute_path(path); 212 // build a new path from path and leaf 213 // copy path first 214 uint32 pathLen = strlen(path); 215 if (pathLen >= sizeof(newPath)) 216 error = B_NAME_TOO_LONG; 217 if (error == B_OK) 218 strcpy(newPath, path); 219 // append leaf, if supplied 220 if (error == B_OK && leaf) { 221 bool needsSeparator = (pathLen > 0 && path[pathLen - 1] != '/'); 222 uint32 wholeLen = pathLen + (needsSeparator ? 1 : 0) 223 + strlen(leaf); 224 if (wholeLen >= sizeof(newPath)) 225 error = B_NAME_TOO_LONG; 226 if (error == B_OK) { 227 if (needsSeparator) { 228 newPath[pathLen] = '/'; 229 pathLen++; 230 } 231 strcpy(newPath + pathLen, leaf); 232 } 233 } 234 // check, if necessary to normalize 235 if (error == B_OK && !normalize) 236 normalize = normalize || _MustNormalize(newPath, &error); 237 238 // normalize the path, if necessary, otherwise just set it 239 if (error == B_OK) { 240 if (normalize) { 241 // create a BEntry and initialize us with this entry 242 BEntry entry; 243 error = entry.SetTo(newPath, false); 244 if (error == B_OK) 245 return SetTo(&entry); 246 } else 247 error = _SetPath(newPath); 248 } 249 } 250 // cleanup, if something went wrong 251 if (error != B_OK) 252 Unset(); 253 fCStatus = error; 254 return error; 255 } 256 257 258 /*! \brief Reinitializes the object to the specified directory and relative 259 path combination. 260 \param dir Refers to the directory that provides the base component of the 261 pathname. 262 \param path the relative path name (may be \c NULL) 263 \param normalize boolean flag used to force normalization; normalization 264 may occur even if false (see \ref _MustNormalize). 265 \return 266 - \c B_OK: The initialization was successful. 267 - \c B_BAD_VALUE: \c NULL \a dir or absolute \a path. 268 - \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH. 269 - other error codes. 270 */ 271 status_t 272 BPath::SetTo(const BDirectory* dir, const char* path, bool normalize) 273 { 274 status_t error = (dir && dir->InitCheck() == B_OK ? B_OK : B_BAD_VALUE); 275 // get the path of the BDirectory 276 BEntry entry; 277 if (error == B_OK) 278 error = dir->GetEntry(&entry); 279 BPath dirPath; 280 if (error == B_OK) 281 error = dirPath.SetTo(&entry); 282 // let the other version do the work 283 if (error == B_OK) 284 error = SetTo(dirPath.Path(), path, normalize); 285 if (error != B_OK) 286 Unset(); 287 fCStatus = error; 288 return error; 289 } 290 291 292 /*! \brief Returns the object to an uninitialized state. The object frees any 293 resources it allocated and marks itself as uninitialized. 294 */ 295 void 296 BPath::Unset() 297 { 298 _SetPath(NULL); 299 fCStatus = B_NO_INIT; 300 } 301 302 303 /*! \brief Appends the given (relative) path to the end of the current path. 304 This call fails if the path is absolute or the object to which you're 305 appending is uninitialized. 306 \param path relative pathname to append to current path (may be \c NULL). 307 \param normalize boolean flag used to force normalization; normalization 308 may occur even if false (see \ref _MustNormalize). 309 \return 310 - \c B_OK: The initialization was successful. 311 - \c B_BAD_VALUE: The object is not properly initialized. 312 - \c B_NAME_TOO_LONG: The pathname is longer than \c B_PATH_NAME_LENGTH. 313 - other error codes. 314 */ 315 status_t 316 BPath::Append(const char* path, bool normalize) 317 { 318 status_t error = (InitCheck() == B_OK ? B_OK : B_BAD_VALUE); 319 if (error == B_OK) 320 error = SetTo(Path(), path, normalize); 321 if (error != B_OK) 322 Unset(); 323 fCStatus = error; 324 return error; 325 } 326 327 328 /*! \brief Returns the object's complete path name. 329 \return 330 - the object's path name, or 331 - \c NULL, if it is not properly initialized. 332 */ 333 const char* 334 BPath::Path() const 335 { 336 return fName; 337 } 338 339 340 /*! \brief Returns the leaf portion of the object's path name. 341 The leaf portion is defined as the string after the last \c '/'. For 342 the root path (\c "/") it is the empty string (\c ""). 343 \return 344 - the leaf portion of the object's path name, or 345 - \c NULL, if it is not properly initialized. 346 */ 347 const char* 348 BPath::Leaf() const 349 { 350 if (InitCheck() != B_OK) 351 return NULL; 352 353 const char* result = fName + strlen(fName); 354 // There should be no need for the second condition, since we deal 355 // with absolute paths only and those contain at least one '/'. 356 // However, it doesn't harm. 357 while (*result != '/' && result > fName) 358 result--; 359 result++; 360 361 return result; 362 } 363 364 365 /*! \brief Calls the argument's SetTo() method with the name of the 366 object's parent directory. 367 No normalization is done. 368 \param path the BPath object to be initialized to the parent directory's 369 path name. 370 \return 371 - \c B_OK: Everything went fine. 372 - \c B_BAD_VALUE: \c NULL \a path. 373 - \c B_ENTRY_NOT_FOUND: The object represents \c "/". 374 - other error code returned by SetTo(). 375 */ 376 status_t 377 BPath::GetParent(BPath* path) const 378 { 379 if (path == NULL) 380 return B_BAD_VALUE; 381 382 status_t error = InitCheck(); 383 if (error != B_OK) 384 return error; 385 386 int32 length = strlen(fName); 387 if (length == 1) { 388 // handle "/" (path is supposed to be absolute) 389 return B_ENTRY_NOT_FOUND; 390 } 391 392 char parentPath[B_PATH_NAME_LENGTH]; 393 length--; 394 while (fName[length] != '/' && length > 0) 395 length--; 396 if (length == 0) { 397 // parent dir is "/" 398 length++; 399 } 400 memcpy(parentPath, fName, length); 401 parentPath[length] = '\0'; 402 403 return path->SetTo(parentPath); 404 } 405 406 407 bool 408 BPath::IsAbsolute() const 409 { 410 if (InitCheck() != B_OK) 411 return false; 412 413 return fName[0] == '/'; 414 } 415 416 417 /*! \brief Performs a simple (string-wise) comparison of paths. 418 No normalization takes place! Uninitialized BPath objects are considered 419 to be equal. 420 \param item the BPath object to be compared with 421 \return \c true, if the path names are equal, \c false otherwise. 422 */ 423 bool 424 BPath::operator==(const BPath& item) const 425 { 426 return *this == item.Path(); 427 } 428 429 430 /*! \brief Performs a simple (string-wise) comparison of paths. 431 No normalization takes place! 432 \param path the path name to be compared with 433 \return \c true, if the path names are equal, \c false otherwise. 434 */ 435 bool 436 BPath::operator==(const char* path) const 437 { 438 return (InitCheck() != B_OK && path == NULL) 439 || (fName != NULL && path != NULL && strcmp(fName, path) == 0); 440 } 441 442 443 /*! \brief Performs a simple (string-wise) comparison of paths. 444 No normalization takes place! Uninitialized BPath objects are considered 445 to be equal. 446 \param item the BPath object to be compared with 447 \return \c true, if the path names are not equal, \c false otherwise. 448 */ 449 bool 450 BPath::operator!=(const BPath& item) const 451 { 452 return !(*this == item); 453 } 454 455 456 /*! \brief Performs a simple (string-wise) comparison of paths. 457 No normalization takes place! 458 \param path the path name to be compared with 459 \return \c true, if the path names are not equal, \c false otherwise. 460 */ 461 bool 462 BPath::operator!=(const char* path) const 463 { 464 return !(*this == path); 465 } 466 467 468 /*! \brief Initializes the object to be a copy of the argument. 469 \param item the BPath object to be copied 470 \return \c *this 471 */ 472 BPath& 473 BPath::operator=(const BPath& item) 474 { 475 if (this != &item) 476 *this = item.Path(); 477 return *this; 478 } 479 480 481 /*! \brief Initializes the object to be a copy of the argument. 482 Has the same effect as \code SetTo(path) \endcode. 483 \param path the path name to be assigned to this object 484 \return \c *this 485 */ 486 BPath& 487 BPath::operator=(const char* path) 488 { 489 if (path == NULL) 490 Unset(); 491 else 492 SetTo(path); 493 return *this; 494 } 495 496 497 // #pragma mark - BFlattenable functionality 498 499 500 // that's the layout of a flattened entry_ref 501 struct flattened_entry_ref { 502 dev_t device; 503 ino_t directory; 504 char name[1]; 505 }; 506 507 // base size of a flattened entry ref 508 static const size_t flattened_entry_ref_size 509 = sizeof(dev_t) + sizeof(ino_t); 510 511 512 /*! \brief Returns \c false. 513 Implements BFlattenable. 514 \return \c false 515 */ 516 bool 517 BPath::IsFixedSize() const 518 { 519 return false; 520 } 521 522 523 /*! \brief Returns \c B_REF_TYPE. 524 Implements BFlattenable. 525 \return \c B_REF_TYPE 526 */ 527 type_code 528 BPath::TypeCode() const 529 { 530 return B_REF_TYPE; 531 } 532 533 534 /*! \brief Returns the size of the flattened entry_ref structure that 535 represents the pathname. 536 Implements BFlattenable. 537 \return the size needed for flattening. 538 */ 539 ssize_t 540 BPath::FlattenedSize() const 541 { 542 ssize_t size = flattened_entry_ref_size; 543 BEntry entry; 544 entry_ref ref; 545 if (InitCheck() == B_OK 546 && entry.SetTo(Path()) == B_OK 547 && entry.GetRef(&ref) == B_OK) { 548 size += strlen(ref.name) + 1; 549 } 550 return size; 551 } 552 553 554 /*! \brief Converts the object's pathname to an entry_ref and writes it into 555 buffer. 556 Implements BFlattenable. 557 \param buffer the buffer the data shall be stored in 558 \param size the size of \a buffer 559 \return 560 - \c B_OK: Everything went fine. 561 - \c B_BAD_VALUE: \c NULL buffer or the buffer is of insufficient size. 562 - other error codes. 563 \todo Reimplement for performance reasons: Don't call FlattenedSize(). 564 */ 565 status_t 566 BPath::Flatten(void* buffer, ssize_t size) const 567 { 568 if (buffer == NULL) 569 return B_BAD_VALUE; 570 571 ssize_t flattenedSize = FlattenedSize(); 572 if (flattenedSize < 0) 573 return flattenedSize; 574 if (size < flattenedSize) 575 return B_BAD_VALUE; 576 status_t status = InitCheck(); 577 if (status != B_OK) 578 return status; 579 580 // convert the path to an entry_ref 581 BEntry entry; 582 entry_ref ref; 583 status = entry.SetTo(Path()); 584 if (status == B_OK) 585 status = entry.GetRef(&ref); 586 if (status != B_OK) 587 return status; 588 589 // store the entry_ref in the buffer 590 flattened_entry_ref& fref = *(flattened_entry_ref*)buffer; 591 fref.device = ref.device; 592 fref.directory = ref.directory; 593 if (ref.name) 594 strcpy(fref.name, ref.name); 595 596 return B_OK; 597 } 598 599 600 /*! \brief Returns \c true if code is \c B_REF_TYPE, and false otherwise. 601 Implements BFlattenable. 602 \param code the type code in question 603 \return \c true if code is \c B_REF_TYPE, and false otherwise. 604 */ 605 bool 606 BPath::AllowsTypeCode(type_code code) const 607 { 608 return code == B_REF_TYPE; 609 } 610 611 612 /*! \brief Initializes the BPath with the flattened entry_ref data that's 613 found in the supplied buffer. 614 The type code must be \c B_REF_TYPE. 615 Implements BFlattenable. 616 \param code the type code of the flattened data 617 \param buf a pointer to the flattened data 618 \param size the number of bytes contained in \a buf 619 \return 620 - \c B_OK: Everything went fine. 621 - \c B_BAD_VALUE: \c NULL buffer or the buffer doesn't contain an 622 entry_ref. 623 - other error codes. 624 */ 625 status_t 626 BPath::Unflatten(type_code code, const void* buffer, ssize_t size) 627 { 628 Unset(); 629 status_t error = B_OK; 630 // check params 631 if (!(code == B_REF_TYPE && buffer != NULL 632 && size >= (ssize_t)flattened_entry_ref_size)) { 633 error = B_BAD_VALUE; 634 } 635 if (error == B_OK) { 636 if (size == (ssize_t)flattened_entry_ref_size) { 637 // already Unset(); 638 } else { 639 // reconstruct the entry_ref from the buffer 640 const flattened_entry_ref& fref 641 = *(const flattened_entry_ref*)buffer; 642 BString name(fref.name, size - flattened_entry_ref_size); 643 entry_ref ref(fref.device, fref.directory, name.String()); 644 error = SetTo(&ref); 645 } 646 } 647 if (error != B_OK) 648 fCStatus = error; 649 return error; 650 } 651 652 653 void BPath::_WarPath1() {} 654 void BPath::_WarPath2() {} 655 void BPath::_WarPath3() {} 656 657 658 /*! \brief Sets the supplied path. 659 The path is copied. If \c NULL, the object's path is set to NULL as well. 660 The object's old path is deleted. 661 \param path the path to be set 662 \return 663 - \c B_OK: Everything went fine. 664 - \c B_NO_MEMORY: Insufficient memory. 665 */ 666 status_t 667 BPath::_SetPath(const char* path) 668 { 669 status_t error = B_OK; 670 const char* oldPath = fName; 671 // set the new path 672 if (path) { 673 fName = new(nothrow) char[strlen(path) + 1]; 674 if (fName) 675 strcpy(fName, path); 676 else 677 error = B_NO_MEMORY; 678 } else 679 fName = NULL; 680 681 // delete the old one 682 delete[] oldPath; 683 return error; 684 } 685 686 687 /*! \brief Checks a path to see if normalization is required. 688 689 The following items require normalization: 690 - Relative pathnames (after concatenation; e.g. "boot/ltj") 691 - The presence of "." or ".." ("/boot/ltj/../ltj/./gwar") 692 - Redundant slashes ("/boot//ltj") 693 - A trailing slash ("/boot/ltj/") 694 695 \param _error A pointer to an error variable that will be set if the input 696 is not a valid path. 697 \return 698 - \c true: \a path requires normalization 699 - \c false: \a path does not require normalization 700 */ 701 bool 702 BPath::_MustNormalize(const char* path, status_t* _error) 703 { 704 // Check for useless input 705 if (path == NULL || path[0] == 0) { 706 if (_error != NULL) 707 *_error = B_BAD_VALUE; 708 return false; 709 } 710 711 int len = strlen(path); 712 713 /* Look for anything in the string that forces us to normalize: 714 + No leading / 715 + any occurence of /./ or /../ or //, or a trailing /. or /.. 716 + a trailing / 717 */; 718 if (path[0] != '/') 719 return true; // not "/*" 720 else if (len == 1) 721 return false; // "/" 722 else if (len > 1 && path[len-1] == '/') 723 return true; // "*/" 724 else { 725 enum ParseState { 726 NoMatch, 727 InitialSlash, 728 OneDot, 729 TwoDots 730 } state = NoMatch; 731 732 for (int i = 0; path[i] != 0; i++) { 733 switch (state) { 734 case NoMatch: 735 if (path[i] == '/') 736 state = InitialSlash; 737 break; 738 739 case InitialSlash: 740 if (path[i] == '/') 741 return true; // "*//*" 742 743 if (path[i] == '.') 744 state = OneDot; 745 else 746 state = NoMatch; 747 break; 748 749 case OneDot: 750 if (path[i] == '/') 751 return true; // "*/./*" 752 753 if (path[i] == '.') 754 state = TwoDots; 755 else 756 state = NoMatch; 757 break; 758 759 case TwoDots: 760 if (path[i] == '/') 761 return true; // "*/../*" 762 763 state = NoMatch; 764 break; 765 } 766 } 767 768 // If we hit the end of the string while in either 769 // of these two states, there was a trailing /. or /.. 770 if (state == OneDot || state == TwoDots) 771 return true; 772 773 return false; 774 } 775 } 776 777 778 /*! 779 \var char *BPath::fName 780 \brief Pointer to the object's path name string. 781 */ 782 783 /*! 784 \var status_t BPath::fCStatus 785 \brief The object's initialization status. 786 */ 787