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> 20using namespace std; 21 22// constructor 23/*! \brief Creates an empty BDiskDeviceList object. 24*/ 25BDiskDeviceList::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*/ 37BDiskDeviceList::~BDiskDeviceList() 38{ 39 delete fLocker; 40} 41 42// MessageReceived 43/*! \brief Implemented to handle notification messages. 44*/ 45void 46BDiskDeviceList::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*/ 106void 107BDiskDeviceList::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*/ 132status_t 133BDiskDeviceList::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*/ 169void 170BDiskDeviceList::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*/ 189bool 190BDiskDeviceList::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*/ 204void 205BDiskDeviceList::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*/ 219int32 220BDiskDeviceList::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*/ 234BDiskDevice * 235BDiskDeviceList::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*/ 253BDiskDevice * 254BDiskDeviceList::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*/ 278BPartition * 279BDiskDeviceList::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*/ 304BPartition * 305BDiskDeviceList::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*/ 334BPartition * 335BDiskDeviceList::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*/ 359BDiskDevice * 360BDiskDeviceList::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*/ 375BPartition * 376BDiskDeviceList::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*/ 389void 390BDiskDeviceList::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*/ 402void 403BDiskDeviceList::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*/ 415void 416BDiskDeviceList::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*/ 428void 429BDiskDeviceList::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*/ 441void 442BDiskDeviceList::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*/ 454void 455BDiskDeviceList::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*/ 467void 468BDiskDeviceList::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*/ 488void 489BDiskDeviceList::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*/ 501void 502BDiskDeviceList::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*/ 514void 515BDiskDeviceList::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*/ 531void 532BDiskDeviceList::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*/ 543void 544BDiskDeviceList::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*/ 555void 556BDiskDeviceList::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*/ 570void 571BDiskDeviceList::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*/ 583status_t 584BDiskDeviceList::_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*/ 600void 601BDiskDeviceList::_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*/ 613void 614BDiskDeviceList::_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*/ 626void 627BDiskDeviceList::_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*/ 639void 640BDiskDeviceList::_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*/ 652void 653BDiskDeviceList::_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*/ 665void 666BDiskDeviceList::_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*/ 678void 679BDiskDeviceList::_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*/ 691void 692BDiskDeviceList::_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*/ 704void 705BDiskDeviceList::_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*/ 719void 720BDiskDeviceList::_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*/ 732void 733BDiskDeviceList::_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*/ 745void 746BDiskDeviceList::_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*/ 756void 757BDiskDeviceList::_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*/ 774void 775BDiskDeviceList::_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*/ 790BDiskDevice * 791BDiskDeviceList::_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.*/ 806BPartition * 807BDiskDeviceList::_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*/ 823BDiskDevice * 824BDiskDeviceList::_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