1/*
2 * Copyright 2004-2018, Haiku, Inc.
3 * Copyright 2003-2004, Ingo Weinhold, bonefish@cs.tu-berlin.de.
4 * All rights reserved. Distributed under the terms of the MIT License.
5 */
6
7
8#include "KDiskDevice.h"
9#include "KDiskDeviceManager.h"
10#include "KDiskDeviceUtils.h"
11#include "KDiskSystem.h"
12#include "KFileDiskDevice.h"
13#include "KFileSystem.h"
14#include "KPartition.h"
15#include "KPartitioningSystem.h"
16#include "KPartitionVisitor.h"
17#include "KPath.h"
18
19#include <VectorMap.h>
20#include <VectorSet.h>
21
22#include <DiskDeviceRoster.h>
23#include <KernelExport.h>
24#include <NodeMonitor.h>
25
26#include <boot_device.h>
27#include <kmodule.h>
28#include <node_monitor.h>
29#include <Notifications.h>
30#include <util/kernel_cpp.h>
31#include <vfs.h>
32
33#include <dirent.h>
34#include <errno.h>
35#include <module.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <sys/stat.h>
40
41
42//#define TRACE_KDISK_DEVICE_MANAGER
43#ifdef TRACE_KDISK_DEVICE_MANAGER
44#	define TRACE		TRACE_ALWAYS
45#else
46#	define TRACE(x...)	do { } while (false)
47#endif
48#define TRACE_ALWAYS(x...)	dprintf("disk_device_manager: " x)
49#define TRACE_ERROR(x...)	dprintf("disk_device_manager: error: " x)
50
51
52// directories for partitioning and file system modules
53static const char* kPartitioningSystemPrefix = "partitioning_systems";
54static const char* kFileSystemPrefix = "file_systems";
55
56
57// singleton instance
58KDiskDeviceManager* KDiskDeviceManager::sDefaultManager = NULL;
59
60
61struct device_event {
62	int32					opcode;
63	const char*				name;
64	dev_t					device;
65	ino_t					directory;
66	ino_t					node;
67};
68
69
70struct GetPartitionID {
71	inline partition_id operator()(const KPartition* partition) const
72	{
73		return partition->ID();
74	}
75};
76
77
78struct GetDiskSystemID {
79	inline disk_system_id operator()(const KDiskSystem* system) const
80	{
81		return system->ID();
82	}
83};
84
85
86struct KDiskDeviceManager::PartitionMap : VectorMap<partition_id, KPartition*,
87	VectorMapEntryStrategy::ImplicitKey<partition_id, KPartition*,
88		GetPartitionID> > {
89};
90
91
92struct KDiskDeviceManager::DeviceMap : VectorMap<partition_id, KDiskDevice*,
93	VectorMapEntryStrategy::ImplicitKey<partition_id, KDiskDevice*,
94		GetPartitionID> > {
95};
96
97
98struct KDiskDeviceManager::DiskSystemMap : VectorMap<disk_system_id,
99	KDiskSystem*,
100	VectorMapEntryStrategy::ImplicitKey<disk_system_id, KDiskSystem*,
101		GetDiskSystemID> > {
102};
103
104
105struct KDiskDeviceManager::PartitionSet : VectorSet<KPartition*> {
106};
107
108
109class KDiskDeviceManager::DiskSystemWatcher : public NotificationListener {
110public:
111	DiskSystemWatcher(KDiskDeviceManager* manager)
112		:
113		fManager(manager)
114	{
115	}
116
117	virtual ~DiskSystemWatcher()
118	{
119	}
120
121	virtual void EventOccurred(NotificationService& service,
122		const KMessage* event)
123	{
124		if (event->GetInt32("opcode", -1) != B_ENTRY_REMOVED)
125			fManager->RescanDiskSystems();
126	}
127
128private:
129	KDiskDeviceManager* fManager;
130};
131
132
133class KDiskDeviceManager::DeviceWatcher : public NotificationListener {
134public:
135	DeviceWatcher()
136	{
137	}
138
139	virtual ~DeviceWatcher()
140	{
141	}
142
143	virtual void EventOccurred(NotificationService& service,
144		const KMessage* event)
145	{
146		int32 opcode = event->GetInt32("opcode", -1);
147		switch (opcode) {
148			case B_ENTRY_CREATED:
149			case B_ENTRY_REMOVED:
150			{
151				device_event* deviceEvent = new(std::nothrow) device_event;
152				if (deviceEvent == NULL)
153					break;
154
155				const char* name = event->GetString("name", NULL);
156				if (name != NULL)
157					deviceEvent->name = strdup(name);
158				else
159					deviceEvent->name = NULL;
160
161				deviceEvent->opcode = opcode;
162				deviceEvent->device = event->GetInt32("device", -1);
163				deviceEvent->directory = event->GetInt64("directory", -1);
164				deviceEvent->node = event->GetInt64("node", -1);
165
166				struct stat stat;
167				if (vfs_stat_node_ref(deviceEvent->device,  deviceEvent->node,
168						&stat) != 0) {
169					delete deviceEvent;
170					break;
171				}
172				if (S_ISDIR(stat.st_mode)) {
173					if (opcode == B_ENTRY_CREATED) {
174						add_node_listener(deviceEvent->device,
175							deviceEvent->node, B_WATCH_DIRECTORY, *this);
176					} else {
177						remove_node_listener(deviceEvent->device,
178							deviceEvent->node, *this);
179					}
180					delete deviceEvent;
181					break;
182				}
183
184				// TODO: a real in-kernel DPC mechanism would be preferred...
185				thread_id thread = spawn_kernel_thread(_HandleDeviceEvent,
186					"device event", B_NORMAL_PRIORITY, deviceEvent);
187				if (thread < 0)
188					delete deviceEvent;
189				else
190					resume_thread(thread);
191				break;
192			}
193
194			default:
195				break;
196		}
197	}
198
199	static status_t _HandleDeviceEvent(void* _event)
200	{
201		device_event* event = (device_event*)_event;
202
203		if (strcmp(event->name, "raw") == 0) {
204			// a new raw device was added/removed
205			KPath path;
206			if (path.InitCheck() != B_OK
207				|| vfs_entry_ref_to_path(event->device, event->directory,
208					event->name, true, path.LockBuffer(),
209					path.BufferSize()) != B_OK) {
210				delete event;
211				return B_ERROR;
212			}
213
214			path.UnlockBuffer();
215			if (event->opcode == B_ENTRY_CREATED)
216				KDiskDeviceManager::Default()->CreateDevice(path.Path());
217			else
218				KDiskDeviceManager::Default()->DeleteDevice(path.Path());
219		}
220
221		delete event;
222		return B_OK;
223	}
224};
225
226
227class KDiskDeviceManager::DiskNotifications
228	: public DefaultUserNotificationService {
229public:
230	DiskNotifications()
231		: DefaultUserNotificationService("disk devices")
232	{
233	}
234
235	virtual ~DiskNotifications()
236	{
237	}
238};
239
240
241//	#pragma mark -
242
243
244KDiskDeviceManager::KDiskDeviceManager()
245	:
246	fDevices(new(nothrow) DeviceMap),
247	fPartitions(new(nothrow) PartitionMap),
248	fDiskSystems(new(nothrow) DiskSystemMap),
249	fObsoletePartitions(new(nothrow) PartitionSet),
250	fMediaChecker(-1),
251	fTerminating(false),
252	fDiskSystemWatcher(NULL),
253	fDeviceWatcher(new(nothrow) DeviceWatcher()),
254	fNotifications(new(nothrow) DiskNotifications)
255{
256	recursive_lock_init(&fLock, "disk device manager");
257
258	if (InitCheck() != B_OK)
259		return;
260
261	fNotifications->Register();
262
263	RescanDiskSystems();
264
265	fMediaChecker = spawn_kernel_thread(_CheckMediaStatusDaemon,
266		"media checker", B_NORMAL_PRIORITY, this);
267	if (fMediaChecker >= 0)
268		resume_thread(fMediaChecker);
269
270	TRACE("number of disk systems: %" B_PRId32 "\n", CountDiskSystems());
271	// TODO: Watch the disk systems and the relevant directories.
272}
273
274
275KDiskDeviceManager::~KDiskDeviceManager()
276{
277	fTerminating = true;
278
279	status_t result;
280	wait_for_thread(fMediaChecker, &result);
281
282	// stop all node monitoring
283	_AddRemoveMonitoring("/dev/disk", false);
284	delete fDeviceWatcher;
285
286	// remove all devices
287	for (int32 cookie = 0; KDiskDevice* device = NextDevice(&cookie);) {
288		PartitionRegistrar _(device);
289		_RemoveDevice(device);
290	}
291
292	// some sanity checks
293	if (fPartitions->Count() > 0) {
294		TRACE_ALWAYS("WARNING: There are still %" B_PRId32 " unremoved partitions!\n",
295			fPartitions->Count());
296		for (PartitionMap::Iterator it = fPartitions->Begin();
297				it != fPartitions->End(); ++it) {
298			TRACE("         partition: %" B_PRId32 "\n", it->Value()->ID());
299		}
300	}
301	if (fObsoletePartitions->Count() > 0) {
302		TRACE_ALWAYS("WARNING: There are still %" B_PRId32 " obsolete partitions!\n",
303				fObsoletePartitions->Count());
304		for (PartitionSet::Iterator it = fObsoletePartitions->Begin();
305				it != fObsoletePartitions->End(); ++it) {
306			TRACE("         partition: %" B_PRId32 "\n", (*it)->ID());
307		}
308	}
309	// remove all disk systems
310	for (int32 cookie = 0; KDiskSystem* diskSystem = NextDiskSystem(&cookie);) {
311		fDiskSystems->Remove(diskSystem->ID());
312		if (diskSystem->IsLoaded()) {
313			TRACE_ALWAYS("WARNING: Disk system `%s' (%" B_PRId32 ") is still loaded!\n",
314				diskSystem->Name(), diskSystem->ID());
315		} else
316			delete diskSystem;
317	}
318
319	fNotifications->Unregister();
320
321	// delete the containers
322	delete fPartitions;
323	delete fDevices;
324	delete fDiskSystems;
325	delete fObsoletePartitions;
326}
327
328
329status_t
330KDiskDeviceManager::InitCheck() const
331{
332	if (fPartitions == NULL || fDevices == NULL || fDiskSystems == NULL
333		|| fObsoletePartitions == NULL || fNotifications == NULL)
334		return B_NO_MEMORY;
335
336	return B_OK;
337}
338
339
340/*!	This creates the system's default DiskDeviceManager.
341	The creation is not thread-safe, and shouldn't be done more than once.
342*/
343status_t
344KDiskDeviceManager::CreateDefault()
345{
346	if (sDefaultManager != NULL)
347		return B_OK;
348
349	sDefaultManager = new(nothrow) KDiskDeviceManager;
350	if (sDefaultManager == NULL)
351		return B_NO_MEMORY;
352
353	return sDefaultManager->InitCheck();
354}
355
356
357/*!	This deletes the default DiskDeviceManager. The deletion is not
358	thread-safe either, you should make sure that it's called only once.
359*/
360void
361KDiskDeviceManager::DeleteDefault()
362{
363	delete sDefaultManager;
364	sDefaultManager = NULL;
365}
366
367
368KDiskDeviceManager*
369KDiskDeviceManager::Default()
370{
371	return sDefaultManager;
372}
373
374
375bool
376KDiskDeviceManager::Lock()
377{
378	return recursive_lock_lock(&fLock) == B_OK;
379}
380
381
382void
383KDiskDeviceManager::Unlock()
384{
385	recursive_lock_unlock(&fLock);
386}
387
388
389DefaultUserNotificationService&
390KDiskDeviceManager::Notifications()
391{
392	return *fNotifications;
393}
394
395
396void
397KDiskDeviceManager::Notify(const KMessage& event, uint32 eventMask)
398{
399	fNotifications->Notify(event, eventMask);
400}
401
402
403KDiskDevice*
404KDiskDeviceManager::FindDevice(const char* path)
405{
406	for (int32 cookie = 0; KDiskDevice* device = NextDevice(&cookie); ) {
407		if (device->Path() && !strcmp(path, device->Path()))
408			return device;
409	}
410	return NULL;
411}
412
413
414KDiskDevice*
415KDiskDeviceManager::FindDevice(partition_id id, bool deviceOnly)
416{
417	if (KPartition* partition = FindPartition(id)) {
418		KDiskDevice* device = partition->Device();
419		if (!deviceOnly || id == device->ID())
420			return device;
421	}
422	return NULL;
423}
424
425
426KPartition*
427KDiskDeviceManager::FindPartition(const char* path)
428{
429	// TODO: Optimize!
430	KPath partitionPath;
431	if (partitionPath.InitCheck() != B_OK)
432		return NULL;
433
434	for (PartitionMap::Iterator iterator = fPartitions->Begin();
435			iterator != fPartitions->End(); ++iterator) {
436		KPartition* partition = iterator->Value();
437		if (partition->GetPath(&partitionPath) == B_OK
438			&& partitionPath == path) {
439			return partition;
440		}
441	}
442
443	return NULL;
444}
445
446
447KPartition*
448KDiskDeviceManager::FindPartition(partition_id id)
449{
450	PartitionMap::Iterator iterator = fPartitions->Find(id);
451	if (iterator != fPartitions->End())
452		return iterator->Value();
453
454	return NULL;
455}
456
457
458KFileDiskDevice*
459KDiskDeviceManager::FindFileDevice(const char* filePath)
460{
461	for (int32 cookie = 0; KDiskDevice* device = NextDevice(&cookie); ) {
462		KFileDiskDevice* fileDevice = dynamic_cast<KFileDiskDevice*>(device);
463		if (fileDevice && fileDevice->FilePath()
464			&& !strcmp(filePath, fileDevice->FilePath())) {
465			return fileDevice;
466		}
467	}
468	return NULL;
469}
470
471
472KDiskDevice*
473KDiskDeviceManager::RegisterDevice(const char* path)
474{
475	if (ManagerLocker locker = this) {
476		for (int32 i = 0; i < 2; i++) {
477			if (KDiskDevice* device = FindDevice(path)) {
478				device->Register();
479				return device;
480			}
481
482			// if the device is not known yet, create it and try again
483			const char* leaf = strrchr(path, '/');
484			if (i == 0 && !strncmp(path, "/dev/disk", 9)
485				&& !strcmp(leaf + 1, "raw") && CreateDevice(path) < B_OK)
486				break;
487		}
488	}
489	return NULL;
490}
491
492
493KDiskDevice*
494KDiskDeviceManager::RegisterDevice(partition_id id, bool deviceOnly)
495{
496	if (ManagerLocker locker = this) {
497		if (KDiskDevice* device = FindDevice(id, deviceOnly)) {
498			device->Register();
499			return device;
500		}
501	}
502	return NULL;
503}
504
505
506KDiskDevice*
507KDiskDeviceManager::RegisterNextDevice(int32* cookie)
508{
509	if (!cookie)
510		return NULL;
511
512	if (ManagerLocker locker = this) {
513		if (KDiskDevice* device = NextDevice(cookie)) {
514			device->Register();
515			return device;
516		}
517	}
518	return NULL;
519}
520
521
522KPartition*
523KDiskDeviceManager::RegisterPartition(const char* path)
524{
525	if (ManagerLocker locker = this) {
526		for (int32 i = 0; i < 2; i++) {
527			if (KPartition* partition = FindPartition(path)) {
528				partition->Register();
529				return partition;
530			}
531
532			// if the device is not known yet, create it and try again
533			const char* leaf = strrchr(path, '/');
534			if (i == 0 && !strncmp(path, "/dev/disk", 9)
535				&& !strcmp(leaf + 1, "raw") && CreateDevice(path) < B_OK)
536				break;
537		}
538	}
539	return NULL;
540}
541
542
543KPartition*
544KDiskDeviceManager::RegisterPartition(partition_id id)
545{
546	if (ManagerLocker locker = this) {
547		if (KPartition* partition = FindPartition(id)) {
548			partition->Register();
549			return partition;
550		}
551	}
552	return NULL;
553}
554
555
556KFileDiskDevice*
557KDiskDeviceManager::RegisterFileDevice(const char* filePath)
558{
559	if (ManagerLocker locker = this) {
560		if (KFileDiskDevice* device = FindFileDevice(filePath)) {
561			device->Register();
562			return device;
563		}
564	}
565	return NULL;
566}
567
568
569KDiskDevice*
570KDiskDeviceManager::ReadLockDevice(partition_id id, bool deviceOnly)
571{
572	// register device
573	KDiskDevice* device = RegisterDevice(id, deviceOnly);
574	if (!device)
575		return NULL;
576	// lock device
577	if (device->ReadLock())
578		return device;
579	device->Unregister();
580	return NULL;
581}
582
583
584KDiskDevice*
585KDiskDeviceManager::WriteLockDevice(partition_id id, bool deviceOnly)
586{
587	// register device
588	KDiskDevice* device = RegisterDevice(id, deviceOnly);
589	if (!device)
590		return NULL;
591	// lock device
592	if (device->WriteLock())
593		return device;
594	device->Unregister();
595	return NULL;
596}
597
598
599KPartition*
600KDiskDeviceManager::ReadLockPartition(partition_id id)
601{
602	// register partition
603	KPartition* partition = RegisterPartition(id);
604	if (!partition)
605		return NULL;
606	// get and register the device
607	KDiskDevice* device = NULL;
608	if (ManagerLocker locker = this) {
609		device = partition->Device();
610		if (device)
611			device->Register();
612	}
613	// lock the device
614	if (device && device->ReadLock()) {
615		// final check, if the partition still belongs to the device
616		if (partition->Device() == device)
617			return partition;
618		device->ReadUnlock();
619	}
620	// cleanup on failure
621	if (device)
622		device->Unregister();
623	partition->Unregister();
624	return NULL;
625}
626
627
628KPartition*
629KDiskDeviceManager::WriteLockPartition(partition_id id)
630{
631	// register partition
632	KPartition* partition = RegisterPartition(id);
633	if (!partition)
634		return NULL;
635	// get and register the device
636	KDiskDevice* device = NULL;
637	if (ManagerLocker locker = this) {
638		device = partition->Device();
639		if (device)
640			device->Register();
641	}
642	// lock the device
643	if (device && device->WriteLock()) {
644		// final check, if the partition still belongs to the device
645		if (partition->Device() == device)
646			return partition;
647		device->WriteUnlock();
648	}
649	// cleanup on failure
650	if (device)
651		device->Unregister();
652	partition->Unregister();
653	return NULL;
654}
655
656
657status_t
658KDiskDeviceManager::ScanPartition(KPartition* partition)
659{
660// TODO: This won't do. Locking the DDM while scanning the partition is not a
661// good idea. Even locking the device doesn't feel right. Marking the partition
662// busy and passing the disk system a temporary clone of the partition_data
663// should work as well.
664	if (DeviceWriteLocker deviceLocker = partition->Device()) {
665		if (ManagerLocker locker = this)
666			return _ScanPartition(partition, false);
667	}
668
669	return B_ERROR;
670}
671
672
673partition_id
674KDiskDeviceManager::CreateDevice(const char* path, bool* newlyCreated)
675{
676	if (!path)
677		return B_BAD_VALUE;
678
679	status_t error = B_ERROR;
680	if (ManagerLocker locker = this) {
681		KDiskDevice* device = FindDevice(path);
682		if (device != NULL) {
683			// we already know this device
684			if (newlyCreated)
685				*newlyCreated = false;
686
687			return device->ID();
688		}
689
690		// create a KDiskDevice for it
691		device = new(nothrow) KDiskDevice;
692		if (!device)
693			return B_NO_MEMORY;
694
695		// initialize and add the device
696		error = device->SetTo(path);
697
698		// Note: Here we are allowed to lock a device although already having
699		// the manager locked, since it is not yet added to the manager.
700		DeviceWriteLocker deviceLocker(device);
701		if (error == B_OK && !deviceLocker.IsLocked())
702			error = B_ERROR;
703		if (error == B_OK && !_AddDevice(device))
704			error = B_NO_MEMORY;
705
706		// cleanup on error
707		if (error != B_OK) {
708			deviceLocker.Unlock();
709			delete device;
710			return error;
711		}
712
713		// scan for partitions
714		_ScanPartition(device, false);
715		device->UnmarkBusy(true);
716
717		_NotifyDeviceEvent(device, B_DEVICE_ADDED,
718			B_DEVICE_REQUEST_DEVICE_LIST);
719
720		if (newlyCreated)
721			*newlyCreated = true;
722
723		return device->ID();
724	}
725
726	return error;
727}
728
729
730status_t
731KDiskDeviceManager::DeleteDevice(const char* path)
732{
733	KDiskDevice* device = FindDevice(path);
734	if (device == NULL)
735		return B_ENTRY_NOT_FOUND;
736
737	PartitionRegistrar _(device, false);
738	if (DeviceWriteLocker locker = device) {
739		if (_RemoveDevice(device))
740			return B_OK;
741	}
742
743	return B_ERROR;
744}
745
746
747partition_id
748KDiskDeviceManager::CreateFileDevice(const char* filePath, bool* newlyCreated)
749{
750	if (!filePath)
751		return B_BAD_VALUE;
752
753	// normalize the file path
754	KPath normalizedFilePath;
755	status_t error = normalizedFilePath.SetTo(filePath, KPath::NORMALIZE);
756	if (error != B_OK)
757		return error;
758	filePath = normalizedFilePath.Path();
759
760	KFileDiskDevice* device = NULL;
761	if (ManagerLocker locker = this) {
762		// check, if the device does already exist
763		if ((device = FindFileDevice(filePath))) {
764			if (newlyCreated)
765				*newlyCreated = false;
766
767			return device->ID();
768		}
769
770		// allocate a KFileDiskDevice
771		device = new(nothrow) KFileDiskDevice;
772		if (!device)
773			return B_NO_MEMORY;
774
775		// initialize and add the device
776		error = device->SetTo(filePath);
777
778		// Note: Here we are allowed to lock a device although already having
779		// the manager locked, since it is not yet added to the manager.
780		DeviceWriteLocker deviceLocker(device);
781		if (error == B_OK && !deviceLocker.IsLocked())
782			error = B_ERROR;
783		if (error == B_OK && !_AddDevice(device))
784			error = B_NO_MEMORY;
785
786		// scan device
787		if (error == B_OK) {
788			_ScanPartition(device, false);
789			device->UnmarkBusy(true);
790
791			_NotifyDeviceEvent(device, B_DEVICE_ADDED,
792				B_DEVICE_REQUEST_DEVICE_LIST);
793
794			if (newlyCreated)
795				*newlyCreated = true;
796
797			return device->ID();
798		}
799
800		// cleanup on failure
801		deviceLocker.Unlock();
802		delete device;
803	} else
804		error = B_ERROR;
805	return error;
806}
807
808
809status_t
810KDiskDeviceManager::DeleteFileDevice(const char* filePath)
811{
812	if (KFileDiskDevice* device = RegisterFileDevice(filePath)) {
813		PartitionRegistrar _(device, true);
814		if (DeviceWriteLocker locker = device) {
815			if (_RemoveDevice(device))
816				return B_OK;
817		}
818	}
819	return B_ERROR;
820}
821
822
823status_t
824KDiskDeviceManager::DeleteFileDevice(partition_id id)
825{
826	if (KDiskDevice* device = RegisterDevice(id)) {
827		PartitionRegistrar _(device, true);
828		if (!dynamic_cast<KFileDiskDevice*>(device) || id != device->ID())
829			return B_ENTRY_NOT_FOUND;
830		if (DeviceWriteLocker locker = device) {
831			if (_RemoveDevice(device))
832				return B_OK;
833		}
834	}
835	return B_ERROR;
836}
837
838
839int32
840KDiskDeviceManager::CountDevices()
841{
842	return fDevices->Count();
843}
844
845
846KDiskDevice*
847KDiskDeviceManager::NextDevice(int32* cookie)
848{
849	if (!cookie)
850		return NULL;
851
852	DeviceMap::Iterator it = fDevices->FindClose(*cookie, false);
853	if (it != fDevices->End()) {
854		KDiskDevice* device = it->Value();
855		*cookie = device->ID() + 1;
856		return device;
857	}
858	return NULL;
859}
860
861
862bool
863KDiskDeviceManager::PartitionAdded(KPartition* partition)
864{
865	return partition && fPartitions->Put(partition->ID(), partition) == B_OK;
866}
867
868
869bool
870KDiskDeviceManager::PartitionRemoved(KPartition* partition)
871{
872	if (partition && partition->PrepareForRemoval()
873		&& fPartitions->Remove(partition->ID())) {
874		// TODO: If adding the partition to the obsolete list fails (due to lack
875		// of memory), we can't do anything about it. We will leak memory then.
876		fObsoletePartitions->Insert(partition);
877		partition->MarkObsolete();
878		return true;
879	}
880	return false;
881}
882
883
884bool
885KDiskDeviceManager::DeletePartition(KPartition* partition)
886{
887	if (partition && partition->IsObsolete()
888		&& partition->CountReferences() == 0
889		&& partition->PrepareForDeletion()
890		&& fObsoletePartitions->Remove(partition)) {
891		delete partition;
892		return true;
893	}
894	return false;
895}
896
897
898KDiskSystem*
899KDiskDeviceManager::FindDiskSystem(const char* name, bool byPrettyName)
900{
901	for (int32 cookie = 0; KDiskSystem* diskSystem = NextDiskSystem(&cookie);) {
902		if (byPrettyName) {
903			if (strcmp(name, diskSystem->PrettyName()) == 0)
904				return diskSystem;
905		} else {
906			if (strcmp(name, diskSystem->Name()) == 0)
907				return diskSystem;
908		}
909	}
910	return NULL;
911}
912
913
914KDiskSystem*
915KDiskDeviceManager::FindDiskSystem(disk_system_id id)
916{
917	DiskSystemMap::Iterator it = fDiskSystems->Find(id);
918	if (it != fDiskSystems->End())
919		return it->Value();
920	return NULL;
921}
922
923
924int32
925KDiskDeviceManager::CountDiskSystems()
926{
927	return fDiskSystems->Count();
928}
929
930
931KDiskSystem*
932KDiskDeviceManager::NextDiskSystem(int32* cookie)
933{
934	if (!cookie)
935		return NULL;
936
937	DiskSystemMap::Iterator it = fDiskSystems->FindClose(*cookie, false);
938	if (it != fDiskSystems->End()) {
939		KDiskSystem* diskSystem = it->Value();
940		*cookie = diskSystem->ID() + 1;
941		return diskSystem;
942	}
943	return NULL;
944}
945
946
947KDiskSystem*
948KDiskDeviceManager::LoadDiskSystem(const char* name, bool byPrettyName)
949{
950	KDiskSystem* diskSystem = NULL;
951	if (ManagerLocker locker = this) {
952		diskSystem = FindDiskSystem(name, byPrettyName);
953		if (diskSystem && diskSystem->Load() != B_OK)
954			diskSystem = NULL;
955	}
956	return diskSystem;
957}
958
959
960KDiskSystem*
961KDiskDeviceManager::LoadDiskSystem(disk_system_id id)
962{
963	KDiskSystem* diskSystem = NULL;
964	if (ManagerLocker locker = this) {
965		diskSystem = FindDiskSystem(id);
966		if (diskSystem && diskSystem->Load() != B_OK)
967			diskSystem = NULL;
968	}
969	return diskSystem;
970}
971
972
973KDiskSystem*
974KDiskDeviceManager::LoadNextDiskSystem(int32* cookie)
975{
976	if (!cookie)
977		return NULL;
978
979	if (ManagerLocker locker = this) {
980		if (KDiskSystem* diskSystem = NextDiskSystem(cookie)) {
981			if (diskSystem->Load() == B_OK) {
982				*cookie = diskSystem->ID() + 1;
983				return diskSystem;
984			}
985		}
986	}
987	return NULL;
988}
989
990
991status_t
992KDiskDeviceManager::InitialDeviceScan()
993{
994	// scan for devices
995	if (ManagerLocker locker = this) {
996		status_t error = _Scan("/dev/disk");
997		if (error != B_OK)
998			return error;
999	}
1000
1001	// scan the devices for partitions
1002	int32 cookie = 0;
1003	status_t status = B_OK;
1004	while (KDiskDevice* device = RegisterNextDevice(&cookie)) {
1005		PartitionRegistrar _(device, true);
1006		if (DeviceWriteLocker deviceLocker = device) {
1007			if (ManagerLocker locker = this) {
1008				status_t error = _ScanPartition(device, false);
1009				device->UnmarkBusy(true);
1010				if (error != B_OK)
1011					status = error;
1012				// Even if we could not scan this partition, we want to try
1013				// and scan the rest. Just because one partition is invalid
1014				// or unscannable does not mean the ones after it are.
1015			} else
1016				return B_ERROR;
1017		} else
1018			return B_ERROR;
1019	}
1020	return status;
1021}
1022
1023
1024status_t
1025KDiskDeviceManager::StartMonitoring()
1026{
1027	// do another scan, this will populate the devfs directories
1028	InitialDeviceScan();
1029
1030	// start monitoring the disk systems
1031	fDiskSystemWatcher = new(std::nothrow) DiskSystemWatcher(this);
1032	if (fDiskSystemWatcher != NULL) {
1033		start_watching_modules(kFileSystemPrefix, *fDiskSystemWatcher);
1034		start_watching_modules(kPartitioningSystemPrefix,
1035			*fDiskSystemWatcher);
1036	}
1037
1038	// start monitoring all dirs under /dev/disk
1039	return _AddRemoveMonitoring("/dev/disk", true);
1040}
1041
1042
1043status_t
1044KDiskDeviceManager::_RescanDiskSystems(DiskSystemMap& addedSystems,
1045	bool fileSystems)
1046{
1047	void* cookie = open_module_list(fileSystems
1048		? kFileSystemPrefix : kPartitioningSystemPrefix);
1049	if (cookie == NULL)
1050		return B_NO_MEMORY;
1051
1052	while (true) {
1053		KPath name;
1054		if (name.InitCheck() != B_OK)
1055			break;
1056		size_t nameLength = name.BufferSize();
1057		if (read_next_module_name(cookie, name.LockBuffer(),
1058				&nameLength) != B_OK) {
1059			break;
1060		}
1061		name.UnlockBuffer();
1062
1063		if (FindDiskSystem(name.Path()))
1064			continue;
1065
1066		if (fileSystems) {
1067			TRACE("file system: %s\n", name.Path());
1068			_AddFileSystem(name.Path());
1069		} else {
1070			TRACE("partitioning system: %s\n", name.Path());
1071			_AddPartitioningSystem(name.Path());
1072		}
1073
1074		if (KDiskSystem* system = FindDiskSystem(name.Path()))
1075			addedSystems.Put(system->ID(), system);
1076	}
1077
1078	close_module_list(cookie);
1079	return B_OK;
1080}
1081
1082
1083/*!	Rescan the existing disk systems. This is called after the boot device
1084	has become available.
1085*/
1086status_t
1087KDiskDeviceManager::RescanDiskSystems()
1088{
1089	DiskSystemMap addedSystems;
1090
1091	Lock();
1092
1093	// rescan for partitioning and file systems
1094	_RescanDiskSystems(addedSystems, false);
1095	_RescanDiskSystems(addedSystems, true);
1096
1097	Unlock();
1098
1099	// rescan existing devices with the new disk systems
1100	int32 cookie = 0;
1101	status_t status = B_OK;
1102	while (KDiskDevice* device = RegisterNextDevice(&cookie)) {
1103		PartitionRegistrar _(device, true);
1104		if (DeviceWriteLocker deviceLocker = device) {
1105			if (ManagerLocker locker = this) {
1106				status_t error = _ScanPartition(device, false, &addedSystems);
1107				device->UnmarkBusy(true);
1108				if (error != B_OK)
1109					status = error;
1110				// See comment in InitialDeviceScan().
1111			} else
1112				return B_ERROR;
1113		} else
1114			return B_ERROR;
1115	}
1116
1117	return status;
1118}
1119
1120
1121status_t
1122KDiskDeviceManager::_AddPartitioningSystem(const char* name)
1123{
1124	if (!name)
1125		return B_BAD_VALUE;
1126
1127	KDiskSystem* diskSystem = new(nothrow) KPartitioningSystem(name);
1128	if (!diskSystem)
1129		return B_NO_MEMORY;
1130	return _AddDiskSystem(diskSystem);
1131}
1132
1133
1134status_t
1135KDiskDeviceManager::_AddFileSystem(const char* name)
1136{
1137	if (!name)
1138		return B_BAD_VALUE;
1139
1140	KDiskSystem* diskSystem = new(nothrow) KFileSystem(name);
1141	if (!diskSystem)
1142		return B_NO_MEMORY;
1143
1144	return _AddDiskSystem(diskSystem);
1145}
1146
1147
1148status_t
1149KDiskDeviceManager::_AddDiskSystem(KDiskSystem* diskSystem)
1150{
1151	if (!diskSystem)
1152		return B_BAD_VALUE;
1153	TRACE("KDiskDeviceManager::_AddDiskSystem(%s)\n", diskSystem->Name());
1154	status_t error = diskSystem->Init();
1155	if (error != B_OK) {
1156		TRACE("  initialization failed: %s\n", strerror(error));
1157	}
1158	if (error == B_OK)
1159		error = fDiskSystems->Put(diskSystem->ID(), diskSystem);
1160	if (error != B_OK)
1161		delete diskSystem;
1162	TRACE("KDiskDeviceManager::_AddDiskSystem() done: %s\n", strerror(error));
1163	return error;
1164}
1165
1166
1167bool
1168KDiskDeviceManager::_AddDevice(KDiskDevice* device)
1169{
1170	if (!device || !PartitionAdded(device))
1171		return false;
1172	if (fDevices->Put(device->ID(), device) == B_OK)
1173		return true;
1174	PartitionRemoved(device);
1175	return false;
1176}
1177
1178
1179bool
1180KDiskDeviceManager::_RemoveDevice(KDiskDevice* device)
1181{
1182	if (device != NULL && fDevices->Remove(device->ID())
1183		&& PartitionRemoved(device)) {
1184		_NotifyDeviceEvent(device, B_DEVICE_REMOVED,
1185			B_DEVICE_REQUEST_DEVICE_LIST);
1186		return true;
1187	}
1188
1189	return false;
1190}
1191
1192
1193#if 0
1194/*!
1195	The device must be write locked, the manager must be locked.
1196*/
1197status_t
1198KDiskDeviceManager::_UpdateBusyPartitions(KDiskDevice *device)
1199{
1200	if (!device)
1201		return B_BAD_VALUE;
1202	// mark all partitions un-busy
1203	struct UnmarkBusyVisitor : KPartitionVisitor {
1204		virtual bool VisitPre(KPartition *partition)
1205		{
1206			partition->ClearFlags(B_PARTITION_BUSY
1207								  | B_PARTITION_DESCENDANT_BUSY);
1208			return false;
1209		}
1210	} visitor;
1211	device->VisitEachDescendant(&visitor);
1212	// Iterate through all job queues and all jobs scheduled or in
1213	// progress and mark their scope busy.
1214	for (int32 cookie = 0;
1215		 KDiskDeviceJobQueue *jobQueue = NextJobQueue(&cookie); ) {
1216		if (jobQueue->Device() != device)
1217			continue;
1218		for (int32 i = jobQueue->ActiveJobIndex();
1219			 KDiskDeviceJob *job = jobQueue->JobAt(i); i++) {
1220			if (job->Status() != B_DISK_DEVICE_JOB_IN_PROGRESS
1221				&& job->Status() != B_DISK_DEVICE_JOB_SCHEDULED) {
1222				continue;
1223			}
1224			KPartition *partition = FindPartition(job->ScopeID());
1225			if (!partition || partition->Device() != device)
1226				continue;
1227			partition->AddFlags(B_PARTITION_BUSY);
1228		}
1229	}
1230	// mark all anscestors of busy partitions descendant busy and all
1231	// descendants busy
1232	struct MarkBusyVisitor : KPartitionVisitor {
1233		virtual bool VisitPre(KPartition *partition)
1234		{
1235			// parent busy => child busy
1236			if (partition->Parent() && partition->Parent()->IsBusy())
1237				partition->AddFlags(B_PARTITION_BUSY);
1238			return false;
1239		}
1240
1241		virtual bool VisitPost(KPartition *partition)
1242		{
1243			// child [descendant] busy => parent descendant busy
1244			if ((partition->IsBusy() || partition->IsDescendantBusy())
1245				&& partition->Parent()) {
1246				partition->Parent()->AddFlags(B_PARTITION_DESCENDANT_BUSY);
1247			}
1248			return false;
1249		}
1250	} visitor2;
1251	device->VisitEachDescendant(&visitor2);
1252	return B_OK;
1253}
1254#endif
1255
1256
1257status_t
1258KDiskDeviceManager::_Scan(const char* path)
1259{
1260	TRACE("KDiskDeviceManager::_Scan(%s)\n", path);
1261	status_t error = B_ENTRY_NOT_FOUND;
1262	struct stat st;
1263	if (lstat(path, &st) < 0) {
1264		return errno;
1265	}
1266	if (S_ISDIR(st.st_mode)) {
1267		// a directory: iterate through its contents
1268		DIR* dir = opendir(path);
1269		if (!dir)
1270			return errno;
1271		while (dirent* entry = readdir(dir)) {
1272			// skip "." and ".."
1273			if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
1274				continue;
1275			KPath entryPath;
1276			if (entryPath.SetPath(path) != B_OK
1277				|| entryPath.Append(entry->d_name) != B_OK) {
1278				continue;
1279			}
1280			if (_Scan(entryPath.Path()) == B_OK)
1281				error = B_OK;
1282		}
1283		closedir(dir);
1284	} else {
1285		// not a directory
1286		// check, if it is named "raw"
1287		int32 len = strlen(path);
1288		int32 leafLen = strlen("/raw");
1289		if (len <= leafLen || strcmp(path + len - leafLen, "/raw"))
1290			return B_ERROR;
1291		if (FindDevice(path) != NULL) {
1292			// we already know this device
1293			return B_OK;
1294		}
1295
1296		TRACE("  found device: %s\n", path);
1297		// create a KDiskDevice for it
1298		KDiskDevice* device = new(nothrow) KDiskDevice;
1299		if (!device)
1300			return B_NO_MEMORY;
1301
1302		// init the KDiskDevice
1303		error = device->SetTo(path);
1304		// add the device
1305		if (error == B_OK && !_AddDevice(device))
1306			error = B_NO_MEMORY;
1307		// cleanup on error
1308		if (error != B_OK)
1309			delete device;
1310	}
1311	return error;
1312}
1313
1314
1315/*!
1316	The device must be write locked, the manager must be locked.
1317*/
1318status_t
1319KDiskDeviceManager::_ScanPartition(KPartition* partition, bool async,
1320	DiskSystemMap* restrictScan)
1321{
1322// TODO: There's no reason why the manager needs to be locked anymore.
1323	if (!partition)
1324		return B_BAD_VALUE;
1325
1326// TODO: Reimplement asynchronous scanning, if we really need it.
1327#if 0
1328	if (async) {
1329		// create a new job queue for the device
1330		KDiskDeviceJobQueue *jobQueue = new(nothrow) KDiskDeviceJobQueue;
1331		if (!jobQueue)
1332			return B_NO_MEMORY;
1333		jobQueue->SetDevice(partition->Device());
1334
1335		// create a job for scanning the device and add it to the job queue
1336		KDiskDeviceJob *job = fJobFactory->CreateScanPartitionJob(partition->ID());
1337		if (!job) {
1338			delete jobQueue;
1339			return B_NO_MEMORY;
1340		}
1341
1342		if (!jobQueue->AddJob(job)) {
1343			delete jobQueue;
1344			delete job;
1345			return B_NO_MEMORY;
1346		}
1347
1348		// add the job queue
1349		status_t error = AddJobQueue(jobQueue);
1350		if (error != B_OK)
1351			delete jobQueue;
1352
1353		return error;
1354	}
1355#endif
1356
1357	// scan synchronously
1358
1359	return _ScanPartition(partition, restrictScan);
1360}
1361
1362
1363status_t
1364KDiskDeviceManager::_ScanPartition(KPartition* partition,
1365	DiskSystemMap* restrictScan)
1366{
1367	// the partition's device must be write-locked
1368	if (partition == NULL)
1369		return B_BAD_VALUE;
1370	if (!partition->Device()->HasMedia() || partition->IsMounted())
1371		return B_OK;
1372
1373	if (partition->CountChildren() > 0) {
1374		// Since this partition has already children, we don't scan it
1375		// again, but only its children.
1376		for (int32 i = 0; KPartition* child = partition->ChildAt(i); i++) {
1377			_ScanPartition(child, restrictScan);
1378		}
1379		return B_OK;
1380	}
1381
1382	KPath partitionPath;
1383	partition->GetPath(&partitionPath);
1384
1385	// This happens with some copy protected CDs or eventually other issues.
1386	// Just ignore the partition...
1387	if (partition->Offset() < 0 || partition->BlockSize() == 0
1388		|| partition->Size() <= 0) {
1389		TRACE_ALWAYS("Partition %s has invalid parameters, ignoring it.\n",
1390			partitionPath.Path());
1391		return B_BAD_DATA;
1392	}
1393
1394	TRACE("KDiskDeviceManager::_ScanPartition(%s)\n", partitionPath.Path());
1395
1396	// publish the partition
1397	status_t error = B_OK;
1398	if (!partition->IsPublished()) {
1399		error = partition->PublishDevice();
1400		if (error != B_OK)
1401			return error;
1402	}
1403
1404	DiskSystemMap* diskSystems = restrictScan;
1405	if (diskSystems == NULL)
1406		diskSystems = fDiskSystems;
1407
1408	// find the disk system that returns the best priority for this partition
1409	float bestPriority = partition->DiskSystemPriority();
1410	KDiskSystem* bestDiskSystem = NULL;
1411	void* bestCookie = NULL;
1412	for (DiskSystemMap::Iterator iterator = diskSystems->Begin();
1413			iterator != diskSystems->End(); iterator++) {
1414		KDiskSystem* diskSystem = iterator->Value();
1415		if (diskSystem->Load() != B_OK)
1416			continue;
1417
1418		TRACE("  trying: %s\n", diskSystem->Name());
1419
1420		void* cookie = NULL;
1421		float priority = diskSystem->Identify(partition, &cookie);
1422
1423		TRACE("  returned: %g\n", priority);
1424
1425		if (priority >= 0 && priority > bestPriority) {
1426			// new best disk system
1427			if (bestDiskSystem != NULL) {
1428				bestDiskSystem->FreeIdentifyCookie(partition, bestCookie);
1429				bestDiskSystem->Unload();
1430			}
1431			bestPriority = priority;
1432			bestDiskSystem = diskSystem;
1433			bestCookie = cookie;
1434		} else {
1435			// disk system doesn't identify the partition or worse than our
1436			// current favorite
1437			if (priority >= 0)
1438				diskSystem->FreeIdentifyCookie(partition, cookie);
1439			diskSystem->Unload();
1440		}
1441	}
1442
1443	// now, if we have found a disk system, let it scan the partition
1444	if (bestDiskSystem != NULL) {
1445		TRACE("  scanning with: %s\n", bestDiskSystem->Name());
1446		error = bestDiskSystem->Scan(partition, bestCookie);
1447		bestDiskSystem->FreeIdentifyCookie(partition, bestCookie);
1448		if (error == B_OK) {
1449			partition->SetDiskSystem(bestDiskSystem, bestPriority);
1450			for (int32 i = 0; KPartition* child = partition->ChildAt(i); i++)
1451				_ScanPartition(child, restrictScan);
1452		} else {
1453			// TODO: Handle the error.
1454			TRACE_ERROR("scanning failed: %s\n", strerror(error));
1455		}
1456
1457		// now we can safely unload the disk system -- it has been loaded by
1458		// the partition(s) and thus will not really be unloaded
1459		bestDiskSystem->Unload();
1460	} else {
1461		// contents not recognized
1462		// nothing to be done -- partitions are created as unrecognized
1463	}
1464
1465	return error;
1466}
1467
1468
1469status_t
1470KDiskDeviceManager::_AddRemoveMonitoring(const char* path, bool add)
1471{
1472	struct stat st;
1473	if (lstat(path, &st) < 0)
1474		return errno;
1475
1476	status_t error = B_ENTRY_NOT_FOUND;
1477	if (S_ISDIR(st.st_mode)) {
1478		if (add) {
1479			error = add_node_listener(st.st_dev, st.st_ino, B_WATCH_DIRECTORY,
1480				*fDeviceWatcher);
1481		} else {
1482			error = remove_node_listener(st.st_dev, st.st_ino,
1483				*fDeviceWatcher);
1484		}
1485		if (error != B_OK)
1486			return error;
1487
1488		DIR* dir = opendir(path);
1489		if (!dir)
1490			return errno;
1491
1492		while (dirent* entry = readdir(dir)) {
1493			// skip "." and ".."
1494			if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
1495				continue;
1496
1497			KPath entryPath;
1498			if (entryPath.SetPath(path) != B_OK
1499				|| entryPath.Append(entry->d_name) != B_OK) {
1500				continue;
1501			}
1502
1503			if (_AddRemoveMonitoring(entryPath.Path(), add) == B_OK)
1504				error = B_OK;
1505		}
1506		closedir(dir);
1507	}
1508
1509	return error;
1510}
1511
1512
1513status_t
1514KDiskDeviceManager::_CheckMediaStatus()
1515{
1516	while (!fTerminating) {
1517		int32 cookie = 0;
1518		while (KDiskDevice* device = RegisterNextDevice(&cookie)) {
1519			PartitionRegistrar _(device, true);
1520			DeviceWriteLocker locker(device);
1521
1522			if (device->IsBusy(true))
1523				continue;
1524
1525			bool hadMedia = device->HasMedia();
1526			bool changedMedia = device->MediaChanged();
1527			device->UpdateMediaStatusIfNeeded();
1528
1529			// Detect if there was any status change since last check.
1530			if ((!device->MediaChanged() && (device->HasMedia() || !hadMedia))
1531				|| !(hadMedia != device->HasMedia()
1532					|| changedMedia != device->MediaChanged()))
1533				continue;
1534
1535			device->MarkBusy(true);
1536			device->UninitializeMedia();
1537
1538			if (device->MediaChanged()) {
1539				dprintf("Media changed from %s\n", device->Path());
1540				device->UpdateGeometry();
1541				_ScanPartition(device, false);
1542				_NotifyDeviceEvent(device, B_DEVICE_MEDIA_CHANGED,
1543					B_DEVICE_REQUEST_DEVICE);
1544			} else if (!device->HasMedia() && hadMedia) {
1545				dprintf("Media removed from %s\n", device->Path());
1546			}
1547
1548			device->UnmarkBusy(true);
1549		}
1550
1551		snooze(1000000);
1552	}
1553
1554	return 0;
1555}
1556
1557
1558status_t
1559KDiskDeviceManager::_CheckMediaStatusDaemon(void* self)
1560{
1561	return ((KDiskDeviceManager*)self)->_CheckMediaStatus();
1562}
1563
1564
1565void
1566KDiskDeviceManager::_NotifyDeviceEvent(KDiskDevice* device, int32 event,
1567	uint32 mask)
1568{
1569	char messageBuffer[512];
1570	KMessage message;
1571	message.SetTo(messageBuffer, sizeof(messageBuffer), B_DEVICE_UPDATE);
1572	message.AddInt32("event", event);
1573	message.AddInt32("id", device->ID());
1574	message.AddString("device", device->Path());
1575
1576	fNotifications->Notify(message, mask);
1577}
1578
1579