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