/*
 * 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>
 
// debugging
//#define DBG(x)
#define DBG(x) x
#define OUT dprintf
 
 
// 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(B_PATH_NAME_LENGTH + 1);
			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);
 
	DBG(OUT("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) {
		DBG(OUT("WARNING: There are still %" B_PRId32 " unremoved partitions!\n",
			fPartitions->Count()));
		for (PartitionMap::Iterator it = fPartitions->Begin();
				it != fPartitions->End(); ++it) {
			DBG(OUT("         partition: %" B_PRId32 "\n", it->Value()->ID()));
		}
	}
	if (fObsoletePartitions->Count() > 0) {
		DBG(OUT("WARNING: There are still %" B_PRId32 " obsolete partitions!\n",
				fObsoletePartitions->Count()));
		for (PartitionSet::Iterator it = fObsoletePartitions->Begin();
				it != fObsoletePartitions->End(); ++it) {
			DBG(OUT("         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()) {
			DBG(OUT("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;
		}
 
		if (error == B_OK) {
			// 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()
{
	status_t error = B_ERROR;
 
	// scan for devices
	if (ManagerLocker locker = this) {
		error = _Scan("/dev/disk");
		if (error != B_OK)
			return error;
	}
 
	// scan the devices for partitions
	int32 cookie = 0;
	while (KDiskDevice* device = RegisterNextDevice(&cookie)) {
		PartitionRegistrar _(device, true);
		if (DeviceWriteLocker deviceLocker = device) {
			if (ManagerLocker locker = this) {
				error = _ScanPartition(device, false);
				device->UnmarkBusy(true);
				if (error != B_OK)
					break;
			} else
				return B_ERROR;
		} else
			return B_ERROR;
	}
	return error;
}
 
 
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) {
			DBG(OUT("file system: %s\n", name.Path()));
			_AddFileSystem(name.Path());
		} else {
			DBG(OUT("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;
	while (KDiskDevice* device = RegisterNextDevice(&cookie)) {
		PartitionRegistrar _(device, true);
		if (DeviceWriteLocker deviceLocker = device) {
			if (ManagerLocker locker = this) {
				status_t status = _ScanPartition(device, false, &addedSystems);
				device->UnmarkBusy(true);
				if (status != B_OK)
					break;
			} else
				return B_ERROR;
		} else
			return B_ERROR;
	}
 
	return B_OK;
}
 
 
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;
	DBG(OUT("KDiskDeviceManager::_AddDiskSystem(%s)\n", diskSystem->Name()));
	status_t error = diskSystem->Init();
	DBG(if (error != B_OK)
		OUT("  initialization failed: %s\n", strerror(error)));
	if (error == B_OK)
		error = fDiskSystems->Put(diskSystem->ID(), diskSystem);
	if (error != B_OK)
		delete diskSystem;
	DBG(OUT("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)
{
	DBG(OUT("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;
		}
 
		DBG(OUT("  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) {
		OUT("Partition %s has invalid parameters, ignoring it.\n",
			partitionPath.Path());
		return B_BAD_DATA;
	}
 
	DBG(
		OUT("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;
 
		DBG(OUT("  trying: %s\n", diskSystem->Name()));
 
		void* cookie = NULL;
		float priority = diskSystem->Identify(partition, &cookie);
 
		DBG(OUT("  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) {
		DBG(OUT("  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.
			DBG(OUT("  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);
}
 

V547 Expression 'error == ((int) 0)' is always true.