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