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