1 /* 2 * Copyright 2003-2006, Haiku Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Ingo Weinhold, bonefish@users.sf.net 7 */ 8 9 #include <DiskDeviceList.h> 10 11 #include <AutoLocker.h> 12 #include <DiskDevice.h> 13 #include <DiskDevicePrivate.h> 14 #include <DiskDeviceRoster.h> 15 #include <Locker.h> 16 #include <Looper.h> 17 #include <Partition.h> 18 19 #include <new> 20 using namespace std; 21 22 // constructor 23 /*! \brief Creates an empty BDiskDeviceList object. 24 */ 25 BDiskDeviceList::BDiskDeviceList(bool useOwnLocker) 26 : fLocker(NULL), 27 fDevices(20, true), 28 fSubscribed(false) 29 { 30 if (useOwnLocker) 31 fLocker = new(nothrow) BLocker("BDiskDeviceList_fLocker"); 32 } 33 34 // destructor 35 /*! \brief Frees all resources associated with the object. 36 */ 37 BDiskDeviceList::~BDiskDeviceList() 38 { 39 delete fLocker; 40 } 41 42 // MessageReceived 43 /*! \brief Implemented to handle notification messages. 44 */ 45 void 46 BDiskDeviceList::MessageReceived(BMessage *message) 47 { 48 AutoLocker<BDiskDeviceList> _(this); 49 switch (message->what) { 50 case B_DEVICE_UPDATE: 51 { 52 uint32 event; 53 if (message->FindInt32("event", (int32*)&event) == B_OK) { 54 switch (event) { 55 case B_DEVICE_MOUNT_POINT_MOVED: 56 _MountPointMoved(message); 57 break; 58 case B_DEVICE_PARTITION_MOUNTED: 59 _PartitionMounted(message); 60 break; 61 case B_DEVICE_PARTITION_UNMOUNTED: 62 _PartitionUnmounted(message); 63 break; 64 case B_DEVICE_PARTITION_INITIALIZED: 65 _PartitionInitialized(message); 66 break; 67 case B_DEVICE_PARTITION_RESIZED: 68 _PartitionResized(message); 69 break; 70 case B_DEVICE_PARTITION_MOVED: 71 _PartitionMoved(message); 72 break; 73 case B_DEVICE_PARTITION_CREATED: 74 _PartitionCreated(message); 75 break; 76 case B_DEVICE_PARTITION_DELETED: 77 _PartitionDeleted(message); 78 break; 79 case B_DEVICE_PARTITION_DEFRAGMENTED: 80 _PartitionDefragmented(message); 81 break; 82 case B_DEVICE_PARTITION_REPAIRED: 83 _PartitionRepaired(message); 84 break; 85 case B_DEVICE_MEDIA_CHANGED: 86 _MediaChanged(message); 87 break; 88 case B_DEVICE_ADDED: 89 _DeviceAdded(message); 90 break; 91 case B_DEVICE_REMOVED: 92 _DeviceRemoved(message); 93 break; 94 } 95 } 96 } 97 default: 98 BHandler::MessageReceived(message); 99 } 100 } 101 102 // SetNextHandler 103 /*! \brief Implemented to unsubscribe from notification services when going 104 to be detached from looper. 105 */ 106 void 107 BDiskDeviceList::SetNextHandler(BHandler *handler) 108 { 109 if (!handler) { 110 AutoLocker<BDiskDeviceList> _(this); 111 if (fSubscribed) 112 _StopWatching(); 113 } 114 BHandler::SetNextHandler(handler); 115 } 116 117 // Fetch 118 /*! \brief Empties the list and refills it according to the current state. 119 120 Furthermore, if added to a looper, the list subscribes to notification 121 services needed to keep the list up-to-date. 122 123 If an error occurs, the list Unset()s itself. 124 125 The object doesn't need to be locked, when this method is invoked. The 126 method does itself try to lock the list, but doesn't fail, if that 127 doesn't succeed. That way an object can be used without locking in a 128 single threaded environment. 129 130 \return \c B_OK, if everything went fine, another error code otherwise. 131 */ 132 status_t 133 BDiskDeviceList::Fetch() 134 { 135 Unset(); 136 AutoLocker<BDiskDeviceList> _(this); 137 // register for notifications 138 status_t error = B_OK; 139 if (Looper()) 140 error = _StartWatching(); 141 // get the devices 142 BDiskDeviceRoster roster; 143 while (error == B_OK) { 144 if (BDiskDevice *device = new(nothrow) BDiskDevice) { 145 status_t status = roster.GetNextDevice(device); 146 if (status == B_OK) 147 fDevices.AddItem(device); 148 else if (status == B_ENTRY_NOT_FOUND) 149 break; 150 else 151 error = status; 152 } else 153 error = B_NO_MEMORY; 154 } 155 // cleanup on error 156 if (error != B_OK) 157 Unset(); 158 return error; 159 } 160 161 // Unset 162 /*! \brief Empties the list and unsubscribes from all notification services. 163 164 The object doesn't need to be locked, when this method is invoked. The 165 method does itself try to lock the list, but doesn't fail, if that 166 doesn't succeed. That way an object can be used without locking in a 167 single threaded environment. 168 */ 169 void 170 BDiskDeviceList::Unset() 171 { 172 AutoLocker<BDiskDeviceList> _(this); 173 // unsubscribe from notification services 174 _StopWatching(); 175 // empty the list 176 fDevices.MakeEmpty(); 177 } 178 179 // Lock 180 /*! \brief Locks the list. 181 182 If on construction it had been specified, that the list shall use an 183 own BLocker, then this locker is locked, otherwise LockLooper() is 184 invoked. 185 186 \return \c true, if the list could be locked successfully, \c false 187 otherwise. 188 */ 189 bool 190 BDiskDeviceList::Lock() 191 { 192 if (fLocker) 193 return fLocker->Lock(); 194 return LockLooper(); 195 } 196 197 // Unlock 198 /*! \brief Unlocks the list. 199 200 If on construction it had been specified, that the list shall use an 201 own BLocker, then this locker is unlocked, otherwise UnlockLooper() is 202 invoked. 203 */ 204 void 205 BDiskDeviceList::Unlock() 206 { 207 if (fLocker) 208 return fLocker->Unlock(); 209 return UnlockLooper(); 210 } 211 212 // CountDevices 213 /*! \brief Returns the number of devices in the list. 214 215 The list must be locked. 216 217 \return The number of devices in the list. 218 */ 219 int32 220 BDiskDeviceList::CountDevices() const 221 { 222 return fDevices.CountItems(); 223 } 224 225 // DeviceAt 226 /*! \brief Retrieves a device by index. 227 228 The list must be locked. 229 230 \param index The list index of the device to be returned. 231 \return The device with index \a index, or \c NULL, if the list is not 232 locked or \a index is out of range. 233 */ 234 BDiskDevice * 235 BDiskDeviceList::DeviceAt(int32 index) const 236 { 237 return fDevices.ItemAt(index); 238 } 239 240 // VisitEachDevice 241 /*! \brief Iterates through the all devices in the list. 242 243 The supplied visitor's Visit(BDiskDevice*) is invoked for each device. 244 If Visit() returns \c true, the iteration is terminated and this method 245 returns the respective device. 246 247 The list must be locked. 248 249 \param visitor The visitor. 250 \return The respective device, if the iteration was terminated early, 251 \c NULL otherwise. 252 */ 253 BDiskDevice * 254 BDiskDeviceList::VisitEachDevice(BDiskDeviceVisitor *visitor) 255 { 256 if (visitor) { 257 for (int32 i = 0; BDiskDevice *device = DeviceAt(i); i++) { 258 if (visitor->Visit(device)) 259 return device; 260 } 261 } 262 return NULL; 263 } 264 265 // VisitEachPartition 266 /*! \brief Iterates through the all devices' partitions. 267 268 The supplied visitor's Visit(BPartition*) is invoked for each partition. 269 If Visit() returns \c true, the iteration is terminated and this method 270 returns the respective partition. 271 272 The list must be locked. 273 274 \param visitor The visitor. 275 \return The respective partition, if the iteration was terminated early, 276 \c NULL otherwise. 277 */ 278 BPartition * 279 BDiskDeviceList::VisitEachPartition(BDiskDeviceVisitor *visitor) 280 { 281 if (visitor) { 282 for (int32 i = 0; BDiskDevice *device = DeviceAt(i); i++) { 283 if (BPartition *partition = device->VisitEachDescendant(visitor)) 284 return partition; 285 } 286 } 287 return NULL; 288 } 289 290 // VisitEachMountedPartition 291 /*! \brief Iterates through the all devices' partitions that are mounted. 292 293 The supplied visitor's Visit(BPartition*) is invoked for each mounted 294 partition. 295 If Visit() returns \c true, the iteration is terminated and this method 296 returns the respective partition. 297 298 The list must be locked. 299 300 \param visitor The visitor. 301 \return The respective partition, if the iteration was terminated early, 302 \c NULL otherwise. 303 */ 304 BPartition * 305 BDiskDeviceList::VisitEachMountedPartition(BDiskDeviceVisitor *visitor) 306 { 307 BPartition *partition = NULL; 308 if (visitor) { 309 struct MountedPartitionFilter : public PartitionFilter { 310 virtual ~MountedPartitionFilter() {}; 311 virtual bool Filter(BPartition *partition, int32 level) 312 { return partition->IsMounted(); } 313 } filter; 314 PartitionFilterVisitor filterVisitor(visitor, &filter); 315 partition = VisitEachPartition(&filterVisitor); 316 } 317 return partition; 318 } 319 320 // VisitEachMountablePartition 321 /*! \brief Iterates through the all devices' partitions that are mountable. 322 323 The supplied visitor's Visit(BPartition*) is invoked for each mountable 324 partition. 325 If Visit() returns \c true, the iteration is terminated and this method 326 returns the respective partition. 327 328 The list must be locked. 329 330 \param visitor The visitor. 331 \return The respective partition, if the iteration was terminated early, 332 \c NULL otherwise. 333 */ 334 BPartition * 335 BDiskDeviceList::VisitEachMountablePartition(BDiskDeviceVisitor *visitor) 336 { 337 BPartition *partition = NULL; 338 if (visitor) { 339 struct MountablePartitionFilter : public PartitionFilter { 340 virtual ~MountablePartitionFilter() {}; 341 virtual bool Filter(BPartition *partition, int32 level) 342 { return partition->ContainsFileSystem(); } 343 } filter; 344 PartitionFilterVisitor filterVisitor(visitor, &filter); 345 partition = VisitEachPartition(&filterVisitor); 346 } 347 return partition; 348 } 349 350 // DeviceWithID 351 /*! \brief Retrieves a device by ID. 352 353 The list must be locked. 354 355 \param id The ID of the device to be returned. 356 \return The device with ID \a id, or \c NULL, if the list is not 357 locked or no device with ID \a id is in the list. 358 */ 359 BDiskDevice * 360 BDiskDeviceList::DeviceWithID(int32 id) const 361 { 362 IDFinderVisitor visitor(id); 363 return const_cast<BDiskDeviceList*>(this)->VisitEachDevice(&visitor); 364 } 365 366 // PartitionWithID 367 /*! \brief Retrieves a partition by ID. 368 369 The list must be locked. 370 371 \param id The ID of the partition to be returned. 372 \return The partition with ID \a id, or \c NULL, if the list is not 373 locked or no partition with ID \a id is in the list. 374 */ 375 BPartition * 376 BDiskDeviceList::PartitionWithID(int32 id) const 377 { 378 IDFinderVisitor visitor(id); 379 return const_cast<BDiskDeviceList*>(this)->VisitEachPartition(&visitor); 380 } 381 382 // MountPointMoved 383 /*! \brief Invoked, when the mount point of a partition has been moved. 384 385 The list is locked, when this method is invoked. 386 387 \param partition The concerned partition. 388 */ 389 void 390 BDiskDeviceList::MountPointMoved(BPartition *partition) 391 { 392 PartitionChanged(partition, B_DEVICE_MOUNT_POINT_MOVED); 393 } 394 395 // PartitionMounted 396 /*! \brief Invoked, when a partition has been mounted. 397 398 The list is locked, when this method is invoked. 399 400 \param partition The concerned partition. 401 */ 402 void 403 BDiskDeviceList::PartitionMounted(BPartition *partition) 404 { 405 PartitionChanged(partition, B_DEVICE_PARTITION_MOUNTED); 406 } 407 408 // PartitionUnmounted 409 /*! \brief Invoked, when a partition has been unmounted. 410 411 The list is locked, when this method is invoked. 412 413 \param partition The concerned partition. 414 */ 415 void 416 BDiskDeviceList::PartitionUnmounted(BPartition *partition) 417 { 418 PartitionChanged(partition, B_DEVICE_PARTITION_UNMOUNTED); 419 } 420 421 // PartitionInitialized 422 /*! \brief Invoked, when a partition has been initialized. 423 424 The list is locked, when this method is invoked. 425 426 \param partition The concerned partition. 427 */ 428 void 429 BDiskDeviceList::PartitionInitialized(BPartition *partition) 430 { 431 PartitionChanged(partition, B_DEVICE_PARTITION_INITIALIZED); 432 } 433 434 // PartitionResized 435 /*! \brief Invoked, when a partition has been resized. 436 437 The list is locked, when this method is invoked. 438 439 \param partition The concerned partition. 440 */ 441 void 442 BDiskDeviceList::PartitionResized(BPartition *partition) 443 { 444 PartitionChanged(partition, B_DEVICE_PARTITION_RESIZED); 445 } 446 447 // PartitionMoved 448 /*! \brief Invoked, when a partition has been moved. 449 450 The list is locked, when this method is invoked. 451 452 \param partition The concerned partition. 453 */ 454 void 455 BDiskDeviceList::PartitionMoved(BPartition *partition) 456 { 457 PartitionChanged(partition, B_DEVICE_PARTITION_MOVED); 458 } 459 460 // PartitionCreated 461 /*! \brief Invoked, when a partition has been created. 462 463 The list is locked, when this method is invoked. 464 465 \param partition The concerned partition. 466 */ 467 void 468 BDiskDeviceList::PartitionCreated(BPartition *partition) 469 { 470 } 471 472 // PartitionDeleted 473 /*! \brief Invoked, when a partition has been deleted. 474 475 The method is called twice for a deleted partition. The first time 476 before the BDiskDevice the partition belongs to has been updated. The 477 \a partition parameter will point to a still valid BPartition object. 478 On the second invocation the device object will have been updated and 479 the partition object will have been deleted -- \a partition will be 480 \c NULL then. 481 482 The list is locked, when this method is invoked. 483 484 \param partition The concerned partition. Only non- \c NULL on the first 485 invocation. 486 \param partitionID The ID of the concerned partition. 487 */ 488 void 489 BDiskDeviceList::PartitionDeleted(BPartition *partition, 490 partition_id partitionID) 491 { 492 } 493 494 // PartitionDefragmented 495 /*! \brief Invoked, when a partition has been defragmented. 496 497 The list is locked, when this method is invoked. 498 499 \param partition The concerned partition. 500 */ 501 void 502 BDiskDeviceList::PartitionDefragmented(BPartition *partition) 503 { 504 PartitionChanged(partition, B_DEVICE_PARTITION_DEFRAGMENTED); 505 } 506 507 // PartitionRepaired 508 /*! \brief Invoked, when a partition has been repaired. 509 510 The list is locked, when this method is invoked. 511 512 \param partition The concerned partition. 513 */ 514 void 515 BDiskDeviceList::PartitionRepaired(BPartition *partition) 516 { 517 PartitionChanged(partition, B_DEVICE_PARTITION_REPAIRED); 518 } 519 520 // PartitionChanged 521 /*! \brief Catch-all method invoked by the \c Partition*() hooks, save by 522 PartitionCreated() and PartitionDeleted(). 523 524 If you're interested only in the fact, that something about the partition 525 changed, you can just override this hook instead of the ones telling you 526 exactly what happened. 527 528 \param partition The concerned partition. 529 \param event The event that occurred, if you are interested in it after all. 530 */ 531 void 532 BDiskDeviceList::PartitionChanged(BPartition *partition, uint32 event) 533 { 534 } 535 536 // MediaChanged 537 /*! \brief Invoked, when the media of a device has been changed. 538 539 The list is locked, when this method is invoked. 540 541 \param device The concerned device. 542 */ 543 void 544 BDiskDeviceList::MediaChanged(BDiskDevice *device) 545 { 546 } 547 548 // DeviceAdded 549 /*! \brief Invoked, when a device has been added. 550 551 The list is locked, when this method is invoked. 552 553 \param device The concerned device. 554 */ 555 void 556 BDiskDeviceList::DeviceAdded(BDiskDevice *device) 557 { 558 } 559 560 // DeviceRemoved 561 /*! \brief Invoked, when a device has been removed. 562 563 The supplied object is already removed from the list and is going to be 564 deleted after the hook returns. 565 566 The list is locked, when this method is invoked. 567 568 \param device The concerned device. 569 */ 570 void 571 BDiskDeviceList::DeviceRemoved(BDiskDevice *device) 572 { 573 } 574 575 // _StartWatching 576 /*! \brief Starts watching for disk device notifications. 577 578 The object must be locked (if possible at all), when this method is 579 invoked. 580 581 \return \c B_OK, if everything went fine, another error code otherwise. 582 */ 583 status_t 584 BDiskDeviceList::_StartWatching() 585 { 586 if (!Looper() || fSubscribed) 587 return B_BAD_VALUE; 588 589 status_t error = BDiskDeviceRoster().StartWatching(BMessenger(this)); 590 fSubscribed = (error == B_OK); 591 return error; 592 } 593 594 // _StopWatching 595 /*! \brief Stop watching for disk device notifications. 596 597 The object must be locked (if possible at all), when this method is 598 invoked. 599 */ 600 void 601 BDiskDeviceList::_StopWatching() 602 { 603 if (fSubscribed) { 604 BDiskDeviceRoster().StopWatching(BMessenger(this)); 605 fSubscribed = false; 606 } 607 } 608 609 // _MountPointMoved 610 /*! \brief Handles a "mount point moved" message. 611 \param message The respective notification message. 612 */ 613 void 614 BDiskDeviceList::_MountPointMoved(BMessage *message) 615 { 616 if (_UpdateDevice(message) != NULL) { 617 if (BPartition *partition = _FindPartition(message)) 618 MountPointMoved(partition); 619 } 620 } 621 622 // _PartitionMounted 623 /*! \brief Handles a "partition mounted" message. 624 \param message The respective notification message. 625 */ 626 void 627 BDiskDeviceList::_PartitionMounted(BMessage *message) 628 { 629 if (_UpdateDevice(message) != NULL) { 630 if (BPartition *partition = _FindPartition(message)) 631 PartitionMounted(partition); 632 } 633 } 634 635 // _PartitionUnmounted 636 /*! \brief Handles a "partition unmounted" message. 637 \param message The respective notification message. 638 */ 639 void 640 BDiskDeviceList::_PartitionUnmounted(BMessage *message) 641 { 642 if (_UpdateDevice(message) != NULL) { 643 if (BPartition *partition = _FindPartition(message)) 644 PartitionUnmounted(partition); 645 } 646 } 647 648 // _PartitionInitialized 649 /*! \brief Handles a "partition initialized" message. 650 \param message The respective notification message. 651 */ 652 void 653 BDiskDeviceList::_PartitionInitialized(BMessage *message) 654 { 655 if (_UpdateDevice(message) != NULL) { 656 if (BPartition *partition = _FindPartition(message)) 657 PartitionInitialized(partition); 658 } 659 } 660 661 // _PartitionResized 662 /*! \brief Handles a "partition resized" message. 663 \param message The respective notification message. 664 */ 665 void 666 BDiskDeviceList::_PartitionResized(BMessage *message) 667 { 668 if (_UpdateDevice(message) != NULL) { 669 if (BPartition *partition = _FindPartition(message)) 670 PartitionResized(partition); 671 } 672 } 673 674 // _PartitionMoved 675 /*! \brief Handles a "partition moved" message. 676 \param message The respective notification message. 677 */ 678 void 679 BDiskDeviceList::_PartitionMoved(BMessage *message) 680 { 681 if (_UpdateDevice(message) != NULL) { 682 if (BPartition *partition = _FindPartition(message)) 683 PartitionMoved(partition); 684 } 685 } 686 687 // _PartitionCreated 688 /*! \brief Handles a "partition created" message. 689 \param message The respective notification message. 690 */ 691 void 692 BDiskDeviceList::_PartitionCreated(BMessage *message) 693 { 694 if (_UpdateDevice(message) != NULL) { 695 if (BPartition *partition = _FindPartition(message)) 696 PartitionCreated(partition); 697 } 698 } 699 700 // _PartitionDeleted 701 /*! \brief Handles a "partition deleted" message. 702 \param message The respective notification message. 703 */ 704 void 705 BDiskDeviceList::_PartitionDeleted(BMessage *message) 706 { 707 if (BPartition *partition = _FindPartition(message)) { 708 partition_id id = partition->ID(); 709 PartitionDeleted(partition, id); 710 if (_UpdateDevice(message)) 711 PartitionDeleted(NULL, id); 712 } 713 } 714 715 // _PartitionDefragmented 716 /*! \brief Handles a "partition defragmented" message. 717 \param message The respective notification message. 718 */ 719 void 720 BDiskDeviceList::_PartitionDefragmented(BMessage *message) 721 { 722 if (_UpdateDevice(message) != NULL) { 723 if (BPartition *partition = _FindPartition(message)) 724 PartitionDefragmented(partition); 725 } 726 } 727 728 // _PartitionRepaired 729 /*! \brief Handles a "partition repaired" message. 730 \param message The respective notification message. 731 */ 732 void 733 BDiskDeviceList::_PartitionRepaired(BMessage *message) 734 { 735 if (_UpdateDevice(message) != NULL) { 736 if (BPartition *partition = _FindPartition(message)) 737 PartitionRepaired(partition); 738 } 739 } 740 741 // _MediaChanged 742 /*! \brief Handles a "media changed" message. 743 \param message The respective notification message. 744 */ 745 void 746 BDiskDeviceList::_MediaChanged(BMessage *message) 747 { 748 if (BDiskDevice *device = _UpdateDevice(message)) 749 MediaChanged(device); 750 } 751 752 // _DeviceAdded 753 /*! \brief Handles a "device added" message. 754 \param message The respective notification message. 755 */ 756 void 757 BDiskDeviceList::_DeviceAdded(BMessage *message) 758 { 759 int32 id; 760 if (message->FindInt32("device_id", &id) == B_OK && !DeviceWithID(id)) { 761 BDiskDevice *device = new(nothrow) BDiskDevice; 762 if (BDiskDeviceRoster().GetDeviceWithID(id, device) == B_OK) { 763 fDevices.AddItem(device); 764 DeviceAdded(device); 765 } else 766 delete device; 767 } 768 } 769 770 // _DeviceRemoved 771 /*! \brief Handles a "device removed" message. 772 \param message The respective notification message. 773 */ 774 void 775 BDiskDeviceList::_DeviceRemoved(BMessage *message) 776 { 777 if (BDiskDevice *device = _FindDevice(message)) { 778 fDevices.RemoveItem(device, false); 779 DeviceRemoved(device); 780 delete device; 781 } 782 } 783 784 // _FindDevice 785 /*! \brief Returns the device for the ID contained in a motification message. 786 \param message The notification message. 787 \return The device with the ID, or \c NULL, if the ID or the device could 788 not be found. 789 */ 790 BDiskDevice * 791 BDiskDeviceList::_FindDevice(BMessage *message) 792 { 793 BDiskDevice *device = NULL; 794 int32 id; 795 if (message->FindInt32("device_id", &id) == B_OK) 796 device = DeviceWithID(id); 797 return device; 798 } 799 800 // _FindPartition 801 /*! \brief Returns the partition for the ID contained in a motification 802 message. 803 \param message The notification message. 804 \return The partition with the ID, or \c NULL, if the ID or the partition 805 could not be found.*/ 806 BPartition * 807 BDiskDeviceList::_FindPartition(BMessage *message) 808 { 809 BPartition *partition = NULL; 810 int32 id; 811 if (message->FindInt32("partition_id", &id) == B_OK) 812 partition = PartitionWithID(id); 813 return partition; 814 } 815 816 // _UpdateDevice 817 /*! \brief Finds the device for the ID contained in a motification message 818 and updates it. 819 \param message The notification message. 820 \return The device with the ID, or \c NULL, if the ID or the device could 821 not be found. 822 */ 823 BDiskDevice * 824 BDiskDeviceList::_UpdateDevice(BMessage *message) 825 { 826 BDiskDevice *device = _FindDevice(message); 827 if (device) { 828 if (device->Update() != B_OK) { 829 fDevices.RemoveItem(device); 830 device = NULL; 831 } 832 } 833 return device; 834 } 835 836