⛏️ index : haiku.git

/*
 * Copyright 2004-2018, Haiku, Inc.
 * Copyright 2003-2004, Ingo Weinhold, bonefish@cs.tu-berlin.de.
 * All rights reserved. Distributed under the terms of the MIT License.
 */


#include "KDiskDevice.h"
#include "KDiskDeviceManager.h"
#include "KDiskDeviceUtils.h"
#include "KDiskSystem.h"
#include "KFileDiskDevice.h"
#include "KFileSystem.h"
#include "KPartition.h"
#include "KPartitioningSystem.h"
#include "KPartitionVisitor.h"
#include "KPath.h"

#include <VectorMap.h>
#include <VectorSet.h>

#include <DiskDeviceRoster.h>
#include <KernelExport.h>
#include <NodeMonitor.h>

#include <boot_device.h>
#include <kmodule.h>
#include <node_monitor.h>
#include <Notifications.h>
#include <util/kernel_cpp.h>
#include <vfs.h>

#include <dirent.h>
#include <errno.h>
#include <module.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>


//#define TRACE_KDISK_DEVICE_MANAGER
#ifdef TRACE_KDISK_DEVICE_MANAGER
#	define TRACE		TRACE_ALWAYS
#else
#	define TRACE(x...)	do { } while (false)
#endif
#define TRACE_ALWAYS(x...)	dprintf("disk_device_manager: " x)
#define TRACE_ERROR(x...)	dprintf("disk_device_manager: error: " x)


// directories for partitioning and file system modules
static const char* kPartitioningSystemPrefix = "partitioning_systems";
static const char* kFileSystemPrefix = "file_systems";


// singleton instance
KDiskDeviceManager* KDiskDeviceManager::sDefaultManager = NULL;


struct device_event {
	int32					opcode;
	const char*				name;
	dev_t					device;
	ino_t					directory;
	ino_t					node;
};


struct GetPartitionID {
	inline partition_id operator()(const KPartition* partition) const
	{
		return partition->ID();
	}
};


struct GetDiskSystemID {
	inline disk_system_id operator()(const KDiskSystem* system) const
	{
		return system->ID();
	}
};


struct KDiskDeviceManager::PartitionMap : VectorMap<partition_id, KPartition*,
	VectorMapEntryStrategy::ImplicitKey<partition_id, KPartition*,
		GetPartitionID> > {
};


struct KDiskDeviceManager::DeviceMap : VectorMap<partition_id, KDiskDevice*,
	VectorMapEntryStrategy::ImplicitKey<partition_id, KDiskDevice*,
		GetPartitionID> > {
};


struct KDiskDeviceManager::DiskSystemMap : VectorMap<disk_system_id,
	KDiskSystem*,
	VectorMapEntryStrategy::ImplicitKey<disk_system_id, KDiskSystem*,
		GetDiskSystemID> > {
};


struct KDiskDeviceManager::PartitionSet : VectorSet<KPartition*> {
};


class KDiskDeviceManager::DiskSystemWatcher : public NotificationListener {
public:
	DiskSystemWatcher(KDiskDeviceManager* manager)
		:
		fManager(manager)
	{
	}

	virtual ~DiskSystemWatcher()
	{
	}

	virtual void EventOccurred(NotificationService& service,
		const KMessage* event)
	{
		if (event->GetInt32("opcode", -1) != B_ENTRY_REMOVED)
			fManager->RescanDiskSystems();
	}

private:
	KDiskDeviceManager* fManager;
};


class KDiskDeviceManager::DeviceWatcher : public NotificationListener {
public:
	DeviceWatcher()
	{
	}

	virtual ~DeviceWatcher()
	{
	}

	virtual void EventOccurred(NotificationService& service,
		const KMessage* event)
	{
		int32 opcode = event->GetInt32("opcode", -1);
		switch (opcode) {
			case B_ENTRY_CREATED:
			case B_ENTRY_REMOVED:
			{
				device_event* deviceEvent = new(std::nothrow) device_event;
				if (deviceEvent == NULL)
					break;

				const char* name = event->GetString("name", NULL);
				if (name != NULL)
					deviceEvent->name = strdup(name);
				else
					deviceEvent->name = NULL;

				deviceEvent->opcode = opcode;
				deviceEvent->device = event->GetInt32("device", -1);
				deviceEvent->directory = event->GetInt64("directory", -1);
				deviceEvent->node = event->GetInt64("node", -1);

				struct stat stat;
				if (vfs_stat_node_ref(deviceEvent->device,  deviceEvent->node,
						&stat) != 0) {
					delete deviceEvent;
					break;
				}
				if (S_ISDIR(stat.st_mode)) {
					if (opcode == B_ENTRY_CREATED) {
						add_node_listener(deviceEvent->device,
							deviceEvent->node, B_WATCH_DIRECTORY, *this);
					} else {
						remove_node_listener(deviceEvent->device,
							deviceEvent->node, *this);
					}
					delete deviceEvent;
					break;
				}

				// TODO: a real in-kernel DPC mechanism would be preferred...
				thread_id thread = spawn_kernel_thread(_HandleDeviceEvent,
					"device event", B_NORMAL_PRIORITY, deviceEvent);
				if (thread < 0)
					delete deviceEvent;
				else
					resume_thread(thread);
				break;
			}

			default:
				break;
		}
	}

	static status_t _HandleDeviceEvent(void* _event)
	{
		device_event* event = (device_event*)_event;

		if (strcmp(event->name, "raw") == 0) {
			// a new raw device was added/removed
			KPath path;
			if (path.InitCheck() != B_OK
				|| vfs_entry_ref_to_path(event->device, event->directory,
					event->name, true, path.LockBuffer(),
					path.BufferSize()) != B_OK) {
				delete event;
				return B_ERROR;
			}

			path.UnlockBuffer();
			if (event->opcode == B_ENTRY_CREATED)
				KDiskDeviceManager::Default()->CreateDevice(path.Path());
			else
				KDiskDeviceManager::Default()->DeleteDevice(path.Path());
		}

		delete event;
		return B_OK;
	}
};


class KDiskDeviceManager::DiskNotifications
	: public DefaultUserNotificationService {
public:
	DiskNotifications()
		: DefaultUserNotificationService("disk devices")
	{
	}

	virtual ~DiskNotifications()
	{
	}
};


//	#pragma mark -


KDiskDeviceManager::KDiskDeviceManager()
	:
	fDevices(new(nothrow) DeviceMap),
	fPartitions(new(nothrow) PartitionMap),
	fDiskSystems(new(nothrow) DiskSystemMap),
	fObsoletePartitions(new(nothrow) PartitionSet),
	fMediaChecker(-1),
	fTerminating(false),
	fDiskSystemWatcher(NULL),
	fDeviceWatcher(new(nothrow) DeviceWatcher()),
	fNotifications(new(nothrow) DiskNotifications)
{
	recursive_lock_init(&fLock, "disk device manager");

	if (InitCheck() != B_OK)
		return;

	fNotifications->Register();

	RescanDiskSystems();

	fMediaChecker = spawn_kernel_thread(_CheckMediaStatusDaemon,
		"media checker", B_NORMAL_PRIORITY, this);
	if (fMediaChecker >= 0)
		resume_thread(fMediaChecker);

	TRACE("number of disk systems: %" B_PRId32 "\n", CountDiskSystems());
	// TODO: Watch the disk systems and the relevant directories.
}


KDiskDeviceManager::~KDiskDeviceManager()
{
	fTerminating = true;

	status_t result;
	wait_for_thread(fMediaChecker, &result);

	// stop all node monitoring
	_AddRemoveMonitoring("/dev/disk", false);
	delete fDeviceWatcher;

	// remove all devices
	for (int32 cookie = 0; KDiskDevice* device = NextDevice(&cookie);) {
		PartitionRegistrar _(device);
		_RemoveDevice(device);
	}

	// some sanity checks
	if (fPartitions->Count() > 0) {
		TRACE_ALWAYS("WARNING: There are still %" B_PRId32 " unremoved partitions!\n",
			fPartitions->Count());
		for (PartitionMap::Iterator it = fPartitions->Begin();
				it != fPartitions->End(); ++it) {
			TRACE("         partition: %" B_PRId32 "\n", it->Value()->ID());
		}
	}
	if (fObsoletePartitions->Count() > 0) {
		TRACE_ALWAYS("WARNING: There are still %" B_PRId32 " obsolete partitions!\n",
				fObsoletePartitions->Count());
		for (PartitionSet::Iterator it = fObsoletePartitions->Begin();
				it != fObsoletePartitions->End(); ++it) {
			TRACE("         partition: %" B_PRId32 "\n", (*it)->ID());
		}
	}
	// remove all disk systems
	for (int32 cookie = 0; KDiskSystem* diskSystem = NextDiskSystem(&cookie);) {
		fDiskSystems->Remove(diskSystem->ID());
		if (diskSystem->IsLoaded()) {
			TRACE_ALWAYS("WARNING: Disk system `%s' (%" B_PRId32 ") is still loaded!\n",
				diskSystem->Name(), diskSystem->ID());
		} else
			delete diskSystem;
	}

	fNotifications->Unregister();

	// delete the containers
	delete fPartitions;
	delete fDevices;
	delete fDiskSystems;
	delete fObsoletePartitions;
}


status_t
KDiskDeviceManager::InitCheck() const
{
	if (fPartitions == NULL || fDevices == NULL || fDiskSystems == NULL
		|| fObsoletePartitions == NULL || fNotifications == NULL)
		return B_NO_MEMORY;

	return B_OK;
}


/*!	This creates the system's default DiskDeviceManager.
	The creation is not thread-safe, and shouldn't be done more than once.
*/
status_t
KDiskDeviceManager::CreateDefault()
{
	if (sDefaultManager != NULL)
		return B_OK;

	sDefaultManager = new(nothrow) KDiskDeviceManager;
	if (sDefaultManager == NULL)
		return B_NO_MEMORY;

	return sDefaultManager->InitCheck();
}


/*!	This deletes the default DiskDeviceManager. The deletion is not
	thread-safe either, you should make sure that it's called only once.
*/
void
KDiskDeviceManager::DeleteDefault()
{
	delete sDefaultManager;
	sDefaultManager = NULL;
}


KDiskDeviceManager*
KDiskDeviceManager::Default()
{
	return sDefaultManager;
}


bool
KDiskDeviceManager::Lock()
{
	return recursive_lock_lock(&fLock) == B_OK;
}


void
KDiskDeviceManager::Unlock()
{
	recursive_lock_unlock(&fLock);
}


DefaultUserNotificationService&
KDiskDeviceManager::Notifications()
{
	return *fNotifications;
}


void
KDiskDeviceManager::Notify(const KMessage& event, uint32 eventMask)
{
	fNotifications->Notify(event, eventMask);
}


KDiskDevice*
KDiskDeviceManager::FindDevice(const char* path)
{
	for (int32 cookie = 0; KDiskDevice* device = NextDevice(&cookie); ) {
		if (device->Path() && !strcmp(path, device->Path()))
			return device;
	}
	return NULL;
}


KDiskDevice*
KDiskDeviceManager::FindDevice(partition_id id, bool deviceOnly)
{
	if (KPartition* partition = FindPartition(id)) {
		KDiskDevice* device = partition->Device();
		if (!deviceOnly || id == device->ID())
			return device;
	}
	return NULL;
}


KPartition*
KDiskDeviceManager::FindPartition(const char* path)
{
	// TODO: Optimize!
	KPath partitionPath;
	if (partitionPath.InitCheck() != B_OK)
		return NULL;

	for (PartitionMap::Iterator iterator = fPartitions->Begin();
			iterator != fPartitions->End(); ++iterator) {
		KPartition* partition = iterator->Value();
		if (partition->GetPath(&partitionPath) == B_OK
			&& partitionPath == path) {
			return partition;
		}
	}

	return NULL;
}


KPartition*
KDiskDeviceManager::FindPartition(partition_id id)
{
	PartitionMap::Iterator iterator = fPartitions->Find(id);
	if (iterator != fPartitions->End())
		return iterator->Value();

	return NULL;
}


KFileDiskDevice*
KDiskDeviceManager::FindFileDevice(const char* filePath)
{
	for (int32 cookie = 0; KDiskDevice* device = NextDevice(&cookie); ) {
		KFileDiskDevice* fileDevice = dynamic_cast<KFileDiskDevice*>(device);
		if (fileDevice && fileDevice->FilePath()
			&& !strcmp(filePath, fileDevice->FilePath())) {
			return fileDevice;
		}
	}
	return NULL;
}


KDiskDevice*
KDiskDeviceManager::RegisterDevice(const char* path)
{
	if (ManagerLocker locker = this) {
		for (int32 i = 0; i < 2; i++) {
			if (KDiskDevice* device = FindDevice(path)) {
				device->Register();
				return device;
			}

			// if the device is not known yet, create it and try again
			const char* leaf = strrchr(path, '/');
			if (i == 0 && !strncmp(path, "/dev/disk", 9)
				&& !strcmp(leaf + 1, "raw") && CreateDevice(path) < B_OK)
				break;
		}
	}
	return NULL;
}


KDiskDevice*
KDiskDeviceManager::RegisterDevice(partition_id id, bool deviceOnly)
{
	if (ManagerLocker locker = this) {
		if (KDiskDevice* device = FindDevice(id, deviceOnly)) {
			device->Register();
			return device;
		}
	}
	return NULL;
}


KDiskDevice*
KDiskDeviceManager::RegisterNextDevice(int32* cookie)
{
	if (!cookie)
		return NULL;

	if (ManagerLocker locker = this) {
		if (KDiskDevice* device = NextDevice(cookie)) {
			device->Register();
			return device;
		}
	}
	return NULL;
}


KPartition*
KDiskDeviceManager::RegisterPartition(const char* path)
{
	if (ManagerLocker locker = this) {
		for (int32 i = 0; i < 2; i++) {
			if (KPartition* partition = FindPartition(path)) {
				partition->Register();
				return partition;
			}

			// if the device is not known yet, create it and try again
			const char* leaf = strrchr(path, '/');
			if (i == 0 && !strncmp(path, "/dev/disk", 9)
				&& !strcmp(leaf + 1, "raw") && CreateDevice(path) < B_OK)
				break;
		}
	}
	return NULL;
}


KPartition*
KDiskDeviceManager::RegisterPartition(partition_id id)
{
	if (ManagerLocker locker = this) {
		if (KPartition* partition = FindPartition(id)) {
			partition->Register();
			return partition;
		}
	}
	return NULL;
}


KFileDiskDevice*
KDiskDeviceManager::RegisterFileDevice(const char* filePath)
{
	if (ManagerLocker locker = this) {
		if (KFileDiskDevice* device = FindFileDevice(filePath)) {
			device->Register();
			return device;
		}
	}
	return NULL;
}


KDiskDevice*
KDiskDeviceManager::ReadLockDevice(partition_id id, bool deviceOnly)
{
	// register device
	KDiskDevice* device = RegisterDevice(id, deviceOnly);
	if (!device)
		return NULL;
	// lock device
	if (device->ReadLock())
		return device;
	device->Unregister();
	return NULL;
}


KDiskDevice*
KDiskDeviceManager::WriteLockDevice(partition_id id, bool deviceOnly)
{
	// register device
	KDiskDevice* device = RegisterDevice(id, deviceOnly);
	if (!device)
		return NULL;
	// lock device
	if (device->WriteLock())
		return device;
	device->Unregister();
	return NULL;
}


KPartition*
KDiskDeviceManager::ReadLockPartition(partition_id id)
{
	// register partition
	KPartition* partition = RegisterPartition(id);
	if (!partition)
		return NULL;
	// get and register the device
	KDiskDevice* device = NULL;
	if (ManagerLocker locker = this) {
		device = partition->Device();
		if (device)
			device->Register();
	}
	// lock the device
	if (device && device->ReadLock()) {
		// final check, if the partition still belongs to the device
		if (partition->Device() == device)
			return partition;
		device->ReadUnlock();
	}
	// cleanup on failure
	if (device)
		device->Unregister();
	partition->Unregister();
	return NULL;
}


KPartition*
KDiskDeviceManager::WriteLockPartition(partition_id id)
{
	// register partition
	KPartition* partition = RegisterPartition(id);
	if (!partition)
		return NULL;
	// get and register the device
	KDiskDevice* device = NULL;
	if (ManagerLocker locker = this) {
		device = partition->Device();
		if (device)
			device->Register();
	}
	// lock the device
	if (device && device->WriteLock()) {
		// final check, if the partition still belongs to the device
		if (partition->Device() == device)
			return partition;
		device->WriteUnlock();
	}
	// cleanup on failure
	if (device)
		device->Unregister();
	partition->Unregister();
	return NULL;
}


status_t
KDiskDeviceManager::ScanPartition(KPartition* partition)
{
// TODO: This won't do. Locking the DDM while scanning the partition is not a
// good idea. Even locking the device doesn't feel right. Marking the partition
// busy and passing the disk system a temporary clone of the partition_data
// should work as well.
	if (DeviceWriteLocker deviceLocker = partition->Device()) {
		if (ManagerLocker locker = this)
			return _ScanPartition(partition, false);
	}

	return B_ERROR;
}


partition_id
KDiskDeviceManager::CreateDevice(const char* path, bool* newlyCreated)
{
	if (!path)
		return B_BAD_VALUE;

	status_t error = B_ERROR;
	if (ManagerLocker locker = this) {
		KDiskDevice* device = FindDevice(path);
		if (device != NULL) {
			// we already know this device
			if (newlyCreated)
				*newlyCreated = false;

			return device->ID();
		}

		// create a KDiskDevice for it
		device = new(nothrow) KDiskDevice;
		if (!device)
			return B_NO_MEMORY;

		// initialize and add the device
		error = device->SetTo(path);

		// Note: Here we are allowed to lock a device although already having
		// the manager locked, since it is not yet added to the manager.
		DeviceWriteLocker deviceLocker(device);
		if (error == B_OK && !deviceLocker.IsLocked())
			error = B_ERROR;
		if (error == B_OK && !_AddDevice(device))
			error = B_NO_MEMORY;

		// cleanup on error
		if (error != B_OK) {
			deviceLocker.Unlock();
			delete device;
			return error;
		}

		// scan for partitions
		_ScanPartition(device, false);
		device->UnmarkBusy(true);

		_NotifyDeviceEvent(device, B_DEVICE_ADDED,
			B_DEVICE_REQUEST_DEVICE_LIST);

		if (newlyCreated)
			*newlyCreated = true;

		return device->ID();
	}

	return error;
}


status_t
KDiskDeviceManager::DeleteDevice(const char* path)
{
	KDiskDevice* device = FindDevice(path);
	if (device == NULL)
		return B_ENTRY_NOT_FOUND;

	PartitionRegistrar _(device, false);
	if (DeviceWriteLocker locker = device) {
		if (_RemoveDevice(device))
			return B_OK;
	}

	return B_ERROR;
}


partition_id
KDiskDeviceManager::CreateFileDevice(const char* filePath, bool* newlyCreated)
{
	if (!filePath)
		return B_BAD_VALUE;

	// normalize the file path
	KPath normalizedFilePath;
	status_t error = normalizedFilePath.SetTo(filePath, KPath::NORMALIZE);
	if (error != B_OK)
		return error;
	filePath = normalizedFilePath.Path();

	KFileDiskDevice* device = NULL;
	if (ManagerLocker locker = this) {
		// check, if the device does already exist
		if ((device = FindFileDevice(filePath))) {
			if (newlyCreated)
				*newlyCreated = false;

			return device->ID();
		}

		// allocate a KFileDiskDevice
		device = new(nothrow) KFileDiskDevice;
		if (!device)
			return B_NO_MEMORY;

		// initialize and add the device
		error = device->SetTo(filePath);

		// Note: Here we are allowed to lock a device although already having
		// the manager locked, since it is not yet added to the manager.
		DeviceWriteLocker deviceLocker(device);
		if (error == B_OK && !deviceLocker.IsLocked())
			error = B_ERROR;
		if (error == B_OK && !_AddDevice(device))
			error = B_NO_MEMORY;

		// scan device
		if (error == B_OK) {
			_ScanPartition(device, false);
			device->UnmarkBusy(true);

			_NotifyDeviceEvent(device, B_DEVICE_ADDED,
				B_DEVICE_REQUEST_DEVICE_LIST);

			if (newlyCreated)
				*newlyCreated = true;

			return device->ID();
		}

		// cleanup on failure
		deviceLocker.Unlock();
		delete device;
	} else
		error = B_ERROR;
	return error;
}


status_t
KDiskDeviceManager::DeleteFileDevice(const char* filePath)
{
	if (KFileDiskDevice* device = RegisterFileDevice(filePath)) {
		PartitionRegistrar _(device, true);
		if (DeviceWriteLocker locker = device) {
			if (_RemoveDevice(device))
				return B_OK;
		}
	}
	return B_ERROR;
}


status_t
KDiskDeviceManager::DeleteFileDevice(partition_id id)
{
	if (KDiskDevice* device = RegisterDevice(id)) {
		PartitionRegistrar _(device, true);
		if (!dynamic_cast<KFileDiskDevice*>(device) || id != device->ID())
			return B_ENTRY_NOT_FOUND;
		if (DeviceWriteLocker locker = device) {
			if (_RemoveDevice(device))
				return B_OK;
		}
	}
	return B_ERROR;
}


int32
KDiskDeviceManager::CountDevices()
{
	return fDevices->Count();
}


KDiskDevice*
KDiskDeviceManager::NextDevice(int32* cookie)
{
	if (!cookie)
		return NULL;

	DeviceMap::Iterator it = fDevices->FindClose(*cookie, false);
	if (it != fDevices->End()) {
		KDiskDevice* device = it->Value();
		*cookie = device->ID() + 1;
		return device;
	}
	return NULL;
}


bool
KDiskDeviceManager::PartitionAdded(KPartition* partition)
{
	return partition && fPartitions->Put(partition->ID(), partition) == B_OK;
}


bool
KDiskDeviceManager::PartitionRemoved(KPartition* partition)
{
	if (partition && partition->PrepareForRemoval()
		&& fPartitions->Remove(partition->ID())) {
		// TODO: If adding the partition to the obsolete list fails (due to lack
		// of memory), we can't do anything about it. We will leak memory then.
		fObsoletePartitions->Insert(partition);
		partition->MarkObsolete();
		return true;
	}
	return false;
}


bool
KDiskDeviceManager::DeletePartition(KPartition* partition)
{
	if (partition && partition->IsObsolete()
		&& partition->CountReferences() == 0
		&& partition->PrepareForDeletion()
		&& fObsoletePartitions->Remove(partition)) {
		delete partition;
		return true;
	}
	return false;
}


KDiskSystem*
KDiskDeviceManager::FindDiskSystem(const char* name, bool byPrettyName)
{
	for (int32 cookie = 0; KDiskSystem* diskSystem = NextDiskSystem(&cookie);) {
		if (byPrettyName) {
			if (strcmp(name, diskSystem->PrettyName()) == 0)
				return diskSystem;
		} else {
			if (strcmp(name, diskSystem->Name()) == 0)
				return diskSystem;
		}
	}
	return NULL;
}


KDiskSystem*
KDiskDeviceManager::FindDiskSystem(disk_system_id id)
{
	DiskSystemMap::Iterator it = fDiskSystems->Find(id);
	if (it != fDiskSystems->End())
		return it->Value();
	return NULL;
}


int32
KDiskDeviceManager::CountDiskSystems()
{
	return fDiskSystems->Count();
}


KDiskSystem*
KDiskDeviceManager::NextDiskSystem(int32* cookie)
{
	if (!cookie)
		return NULL;

	DiskSystemMap::Iterator it = fDiskSystems->FindClose(*cookie, false);
	if (it != fDiskSystems->End()) {
		KDiskSystem* diskSystem = it->Value();
		*cookie = diskSystem->ID() + 1;
		return diskSystem;
	}
	return NULL;
}


KDiskSystem*
KDiskDeviceManager::LoadDiskSystem(const char* name, bool byPrettyName)
{
	KDiskSystem* diskSystem = NULL;
	if (ManagerLocker locker = this) {
		diskSystem = FindDiskSystem(name, byPrettyName);
		if (diskSystem && diskSystem->Load() != B_OK)
			diskSystem = NULL;
	}
	return diskSystem;
}


KDiskSystem*
KDiskDeviceManager::LoadDiskSystem(disk_system_id id)
{
	KDiskSystem* diskSystem = NULL;
	if (ManagerLocker locker = this) {
		diskSystem = FindDiskSystem(id);
		if (diskSystem && diskSystem->Load() != B_OK)
			diskSystem = NULL;
	}
	return diskSystem;
}


KDiskSystem*
KDiskDeviceManager::LoadNextDiskSystem(int32* cookie)
{
	if (!cookie)
		return NULL;

	if (ManagerLocker locker = this) {
		if (KDiskSystem* diskSystem = NextDiskSystem(cookie)) {
			if (diskSystem->Load() == B_OK) {
				*cookie = diskSystem->ID() + 1;
				return diskSystem;
			}
		}
	}
	return NULL;
}


status_t
KDiskDeviceManager::InitialDeviceScan()
{
	// scan for devices
	if (ManagerLocker locker = this) {
		status_t error = _Scan("/dev/disk");
		if (error != B_OK)
			return error;
	}

	// scan the devices for partitions
	int32 cookie = 0;
	status_t status = B_OK;
	while (KDiskDevice* device = RegisterNextDevice(&cookie)) {
		PartitionRegistrar _(device, true);
		if (DeviceWriteLocker deviceLocker = device) {
			if (ManagerLocker locker = this) {
				status_t error = _ScanPartition(device, false);
				device->UnmarkBusy(true);
				if (error != B_OK)
					status = error;
				// Even if we could not scan this partition, we want to try
				// and scan the rest. Just because one partition is invalid
				// or unscannable does not mean the ones after it are.
			} else
				return B_ERROR;
		} else
			return B_ERROR;
	}
	return status;
}


status_t
KDiskDeviceManager::StartMonitoring()
{
	// do another scan, this will populate the devfs directories
	InitialDeviceScan();

	// start monitoring the disk systems
	fDiskSystemWatcher = new(std::nothrow) DiskSystemWatcher(this);
	if (fDiskSystemWatcher != NULL) {
		start_watching_modules(kFileSystemPrefix, *fDiskSystemWatcher);
		start_watching_modules(kPartitioningSystemPrefix,
			*fDiskSystemWatcher);
	}

	// start monitoring all dirs under /dev/disk
	return _AddRemoveMonitoring("/dev/disk", true);
}


status_t
KDiskDeviceManager::_RescanDiskSystems(DiskSystemMap& addedSystems,
	bool fileSystems)
{
	void* cookie = open_module_list(fileSystems
		? kFileSystemPrefix : kPartitioningSystemPrefix);
	if (cookie == NULL)
		return B_NO_MEMORY;

	while (true) {
		KPath name;
		if (name.InitCheck() != B_OK)
			break;
		size_t nameLength = name.BufferSize();
		if (read_next_module_name(cookie, name.LockBuffer(),
				&nameLength) != B_OK) {
			break;
		}
		name.UnlockBuffer();

		if (FindDiskSystem(name.Path()))
			continue;

		if (fileSystems) {
			TRACE("file system: %s\n", name.Path());
			_AddFileSystem(name.Path());
		} else {
			TRACE("partitioning system: %s\n", name.Path());
			_AddPartitioningSystem(name.Path());
		}

		if (KDiskSystem* system = FindDiskSystem(name.Path()))
			addedSystems.Put(system->ID(), system);
	}

	close_module_list(cookie);
	return B_OK;
}


/*!	Rescan the existing disk systems. This is called after the boot device
	has become available.
*/
status_t
KDiskDeviceManager::RescanDiskSystems()
{
	DiskSystemMap addedSystems;

	Lock();

	// rescan for partitioning and file systems
	_RescanDiskSystems(addedSystems, false);
	_RescanDiskSystems(addedSystems, true);

	Unlock();

	// rescan existing devices with the new disk systems
	int32 cookie = 0;
	status_t status = B_OK;
	while (KDiskDevice* device = RegisterNextDevice(&cookie)) {
		PartitionRegistrar _(device, true);
		if (DeviceWriteLocker deviceLocker = device) {
			if (ManagerLocker locker = this) {
				status_t error = _ScanPartition(device, false, &addedSystems);
				device->UnmarkBusy(true);
				if (error != B_OK)
					status = error;
				// See comment in InitialDeviceScan().
			} else
				return B_ERROR;
		} else
			return B_ERROR;
	}

	return status;
}


status_t
KDiskDeviceManager::_AddPartitioningSystem(const char* name)
{
	if (!name)
		return B_BAD_VALUE;

	KDiskSystem* diskSystem = new(nothrow) KPartitioningSystem(name);
	if (!diskSystem)
		return B_NO_MEMORY;
	return _AddDiskSystem(diskSystem);
}


status_t
KDiskDeviceManager::_AddFileSystem(const char* name)
{
	if (!name)
		return B_BAD_VALUE;

	KDiskSystem* diskSystem = new(nothrow) KFileSystem(name);
	if (!diskSystem)
		return B_NO_MEMORY;

	return _AddDiskSystem(diskSystem);
}


status_t
KDiskDeviceManager::_AddDiskSystem(KDiskSystem* diskSystem)
{
	if (!diskSystem)
		return B_BAD_VALUE;
	TRACE("KDiskDeviceManager::_AddDiskSystem(%s)\n", diskSystem->Name());
	status_t error = diskSystem->Init();
	if (error != B_OK) {
		TRACE("  initialization failed: %s\n", strerror(error));
	}
	if (error == B_OK)
		error = fDiskSystems->Put(diskSystem->ID(), diskSystem);
	if (error != B_OK)
		delete diskSystem;
	TRACE("KDiskDeviceManager::_AddDiskSystem() done: %s\n", strerror(error));
	return error;
}


bool
KDiskDeviceManager::_AddDevice(KDiskDevice* device)
{
	if (!device || !PartitionAdded(device))
		return false;
	if (fDevices->Put(device->ID(), device) == B_OK)
		return true;
	PartitionRemoved(device);
	return false;
}


bool
KDiskDeviceManager::_RemoveDevice(KDiskDevice* device)
{
	if (device != NULL && fDevices->Remove(device->ID())
		&& PartitionRemoved(device)) {
		_NotifyDeviceEvent(device, B_DEVICE_REMOVED,
			B_DEVICE_REQUEST_DEVICE_LIST);
		return true;
	}

	return false;
}


#if 0
/*!
	The device must be write locked, the manager must be locked.
*/
status_t
KDiskDeviceManager::_UpdateBusyPartitions(KDiskDevice *device)
{
	if (!device)
		return B_BAD_VALUE;
	// mark all partitions un-busy
	struct UnmarkBusyVisitor : KPartitionVisitor {
		virtual bool VisitPre(KPartition *partition)
		{
			partition->ClearFlags(B_PARTITION_BUSY
								  | B_PARTITION_DESCENDANT_BUSY);
			return false;
		}
	} visitor;
	device->VisitEachDescendant(&visitor);
	// Iterate through all job queues and all jobs scheduled or in
	// progress and mark their scope busy.
	for (int32 cookie = 0;
		 KDiskDeviceJobQueue *jobQueue = NextJobQueue(&cookie); ) {
		if (jobQueue->Device() != device)
			continue;
		for (int32 i = jobQueue->ActiveJobIndex();
			 KDiskDeviceJob *job = jobQueue->JobAt(i); i++) {
			if (job->Status() != B_DISK_DEVICE_JOB_IN_PROGRESS
				&& job->Status() != B_DISK_DEVICE_JOB_SCHEDULED) {
				continue;
			}
			KPartition *partition = FindPartition(job->ScopeID());
			if (!partition || partition->Device() != device)
				continue;
			partition->AddFlags(B_PARTITION_BUSY);
		}
	}
	// mark all anscestors of busy partitions descendant busy and all
	// descendants busy
	struct MarkBusyVisitor : KPartitionVisitor {
		virtual bool VisitPre(KPartition *partition)
		{
			// parent busy => child busy
			if (partition->Parent() && partition->Parent()->IsBusy())
				partition->AddFlags(B_PARTITION_BUSY);
			return false;
		}

		virtual bool VisitPost(KPartition *partition)
		{
			// child [descendant] busy => parent descendant busy
			if ((partition->IsBusy() || partition->IsDescendantBusy())
				&& partition->Parent()) {
				partition->Parent()->AddFlags(B_PARTITION_DESCENDANT_BUSY);
			}
			return false;
		}
	} visitor2;
	device->VisitEachDescendant(&visitor2);
	return B_OK;
}
#endif


status_t
KDiskDeviceManager::_Scan(const char* path)
{
	TRACE("KDiskDeviceManager::_Scan(%s)\n", path);
	status_t error = B_ENTRY_NOT_FOUND;
	struct stat st;
	if (lstat(path, &st) < 0) {
		return errno;
	}
	if (S_ISDIR(st.st_mode)) {
		// a directory: iterate through its contents
		DIR* dir = opendir(path);
		if (!dir)
			return errno;
		while (dirent* entry = readdir(dir)) {
			// skip "." and ".."
			if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
				continue;
			KPath entryPath;
			if (entryPath.SetPath(path) != B_OK
				|| entryPath.Append(entry->d_name) != B_OK) {
				continue;
			}
			if (_Scan(entryPath.Path()) == B_OK)
				error = B_OK;
		}
		closedir(dir);
	} else {
		// not a directory
		// check, if it is named "raw"
		int32 len = strlen(path);
		int32 leafLen = strlen("/raw");
		if (len <= leafLen || strcmp(path + len - leafLen, "/raw"))
			return B_ERROR;
		if (FindDevice(path) != NULL) {
			// we already know this device
			return B_OK;
		}

		TRACE("  found device: %s\n", path);
		// create a KDiskDevice for it
		KDiskDevice* device = new(nothrow) KDiskDevice;
		if (!device)
			return B_NO_MEMORY;

		// init the KDiskDevice
		error = device->SetTo(path);
		// add the device
		if (error == B_OK && !_AddDevice(device))
			error = B_NO_MEMORY;
		// cleanup on error
		if (error != B_OK)
			delete device;
	}
	return error;
}


/*!
	The device must be write locked, the manager must be locked.
*/
status_t
KDiskDeviceManager::_ScanPartition(KPartition* partition, bool async,
	DiskSystemMap* restrictScan)
{
// TODO: There's no reason why the manager needs to be locked anymore.
	if (!partition)
		return B_BAD_VALUE;

// TODO: Reimplement asynchronous scanning, if we really need it.
#if 0
	if (async) {
		// create a new job queue for the device
		KDiskDeviceJobQueue *jobQueue = new(nothrow) KDiskDeviceJobQueue;
		if (!jobQueue)
			return B_NO_MEMORY;
		jobQueue->SetDevice(partition->Device());

		// create a job for scanning the device and add it to the job queue
		KDiskDeviceJob *job = fJobFactory->CreateScanPartitionJob(partition->ID());
		if (!job) {
			delete jobQueue;
			return B_NO_MEMORY;
		}

		if (!jobQueue->AddJob(job)) {
			delete jobQueue;
			delete job;
			return B_NO_MEMORY;
		}

		// add the job queue
		status_t error = AddJobQueue(jobQueue);
		if (error != B_OK)
			delete jobQueue;

		return error;
	}
#endif

	// scan synchronously

	return _ScanPartition(partition, restrictScan);
}


status_t
KDiskDeviceManager::_ScanPartition(KPartition* partition,
	DiskSystemMap* restrictScan)
{
	// the partition's device must be write-locked
	if (partition == NULL)
		return B_BAD_VALUE;
	if (!partition->Device()->HasMedia() || partition->IsMounted())
		return B_OK;

	if (partition->CountChildren() > 0) {
		// Since this partition has already children, we don't scan it
		// again, but only its children.
		for (int32 i = 0; KPartition* child = partition->ChildAt(i); i++) {
			_ScanPartition(child, restrictScan);
		}
		return B_OK;
	}

	KPath partitionPath;
	partition->GetPath(&partitionPath);

	// This happens with some copy protected CDs or eventually other issues.
	// Just ignore the partition...
	if (partition->Offset() < 0 || partition->BlockSize() == 0
		|| partition->Size() <= 0) {
		TRACE_ALWAYS("Partition %s has invalid parameters, ignoring it.\n",
			partitionPath.Path());
		return B_BAD_DATA;
	}

	TRACE("KDiskDeviceManager::_ScanPartition(%s)\n", partitionPath.Path());

	// publish the partition
	status_t error = B_OK;
	if (!partition->IsPublished()) {
		error = partition->PublishDevice();
		if (error != B_OK)
			return error;
	}

	DiskSystemMap* diskSystems = restrictScan;
	if (diskSystems == NULL)
		diskSystems = fDiskSystems;

	// find the disk system that returns the best priority for this partition
	float bestPriority = partition->DiskSystemPriority();
	KDiskSystem* bestDiskSystem = NULL;
	void* bestCookie = NULL;
	for (DiskSystemMap::Iterator iterator = diskSystems->Begin();
			iterator != diskSystems->End(); iterator++) {
		KDiskSystem* diskSystem = iterator->Value();
		if (diskSystem->Load() != B_OK)
			continue;

		TRACE("  trying: %s\n", diskSystem->Name());

		void* cookie = NULL;
		float priority = diskSystem->Identify(partition, &cookie);

		TRACE("  returned: %g\n", priority);

		if (priority >= 0 && priority > bestPriority) {
			// new best disk system
			if (bestDiskSystem != NULL) {
				bestDiskSystem->FreeIdentifyCookie(partition, bestCookie);
				bestDiskSystem->Unload();
			}
			bestPriority = priority;
			bestDiskSystem = diskSystem;
			bestCookie = cookie;
		} else {
			// disk system doesn't identify the partition or worse than our
			// current favorite
			if (priority >= 0)
				diskSystem->FreeIdentifyCookie(partition, cookie);
			diskSystem->Unload();
		}
	}

	// now, if we have found a disk system, let it scan the partition
	if (bestDiskSystem != NULL) {
		TRACE("  scanning with: %s\n", bestDiskSystem->Name());
		error = bestDiskSystem->Scan(partition, bestCookie);
		bestDiskSystem->FreeIdentifyCookie(partition, bestCookie);
		if (error == B_OK) {
			partition->SetDiskSystem(bestDiskSystem, bestPriority);
			for (int32 i = 0; KPartition* child = partition->ChildAt(i); i++)
				_ScanPartition(child, restrictScan);
		} else {
			// TODO: Handle the error.
			TRACE_ERROR("scanning failed: %s\n", strerror(error));
		}

		// now we can safely unload the disk system -- it has been loaded by
		// the partition(s) and thus will not really be unloaded
		bestDiskSystem->Unload();
	} else {
		// contents not recognized
		// nothing to be done -- partitions are created as unrecognized
	}

	return error;
}


status_t
KDiskDeviceManager::_AddRemoveMonitoring(const char* path, bool add)
{
	struct stat st;
	if (lstat(path, &st) < 0)
		return errno;

	status_t error = B_ENTRY_NOT_FOUND;
	if (S_ISDIR(st.st_mode)) {
		if (add) {
			error = add_node_listener(st.st_dev, st.st_ino, B_WATCH_DIRECTORY,
				*fDeviceWatcher);
		} else {
			error = remove_node_listener(st.st_dev, st.st_ino,
				*fDeviceWatcher);
		}
		if (error != B_OK)
			return error;

		DIR* dir = opendir(path);
		if (!dir)
			return errno;

		while (dirent* entry = readdir(dir)) {
			// skip "." and ".."
			if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
				continue;

			KPath entryPath;
			if (entryPath.SetPath(path) != B_OK
				|| entryPath.Append(entry->d_name) != B_OK) {
				continue;
			}

			if (_AddRemoveMonitoring(entryPath.Path(), add) == B_OK)
				error = B_OK;
		}
		closedir(dir);
	}

	return error;
}


status_t
KDiskDeviceManager::_CheckMediaStatus()
{
	while (!fTerminating) {
		int32 cookie = 0;
		while (KDiskDevice* device = RegisterNextDevice(&cookie)) {
			PartitionRegistrar _(device, true);
			DeviceWriteLocker locker(device);

			if (device->IsBusy(true))
				continue;

			bool hadMedia = device->HasMedia();
			bool changedMedia = device->MediaChanged();
			device->UpdateMediaStatusIfNeeded();

			// Detect if there was any status change since last check.
			if ((!device->MediaChanged() && (device->HasMedia() || !hadMedia))
				|| !(hadMedia != device->HasMedia()
					|| changedMedia != device->MediaChanged()))
				continue;

			device->MarkBusy(true);
			device->UninitializeMedia();

			if (device->MediaChanged()) {
				dprintf("Media changed from %s\n", device->Path());
				device->UpdateGeometry();
				_ScanPartition(device, false);
				_NotifyDeviceEvent(device, B_DEVICE_MEDIA_CHANGED,
					B_DEVICE_REQUEST_DEVICE);
			} else if (!device->HasMedia() && hadMedia) {
				dprintf("Media removed from %s\n", device->Path());
			}

			device->UnmarkBusy(true);
		}

		snooze(1000000);
	}

	return 0;
}


status_t
KDiskDeviceManager::_CheckMediaStatusDaemon(void* self)
{
	return ((KDiskDeviceManager*)self)->_CheckMediaStatus();
}


void
KDiskDeviceManager::_NotifyDeviceEvent(KDiskDevice* device, int32 event,
	uint32 mask)
{
	char messageBuffer[512];
	KMessage message;
	message.SetTo(messageBuffer, sizeof(messageBuffer), B_DEVICE_UPDATE);
	message.AddInt32("event", event);
	message.AddInt32("id", device->ID());
	message.AddString("device", device->Path());

	fNotifications->Notify(message, mask);
}