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