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