⛏️ index : haiku.git

// VolumeManager.cpp

#include "VolumeManager.h"

#include <new>

#include <sys/stat.h>

#include <AutoDeleter.h>
#include <Entry.h>
#include <fs_info.h>
#include <fs_query.h>
#include <HashMap.h>
#include <NodeMonitor.h>
#include <Volume.h>

#include "ClientVolume.h"
#include "DebugSupport.h"
#include "Directory.h"
#include "Entry.h"
#include "FDManager.h"
#include "NodeHandle.h"
#include "Path.h"
#include "QueryDomain.h"
#include "Volume.h"

// TODO: We should filter recent events at some point. Otherwise we'll end up
// with one event of each kind for each entry/node.

const bigtime_t kRecentEventLifeTime = 100000;	// 0.1 s

// QueryHandler
class VolumeManager::QueryHandler : public BHandler, public QueryListener,
	public BReferenceable {
public:
	QueryHandler(NodeMonitorListener* listener, QueryDomain* queryDomain,
		QueryHandle* handle)
		:
		BHandler(),
		QueryListener(),
		BReferenceable(),
		fListener(listener),
		fQueryDomain(queryDomain),
		fHandle(handle)
	{
	}

	QueryDomain* GetQueryDomain() const
	{
		return fQueryDomain;
	}

	QueryHandle* GetQueryHandle() const
	{
		return fHandle;
	}

	virtual void MessageReceived(BMessage* message)
	{
		switch (message->what) {
			case B_QUERY_UPDATE:
			{
				NodeMonitoringEvent* event = NULL;
				int32 opcode;
				if (message->FindInt32("opcode", &opcode) == B_OK) {
					switch (opcode) {
						case B_ENTRY_CREATED:
							event = new(std::nothrow) EntryCreatedEvent;
							break;
						case B_ENTRY_REMOVED:
							event = new(std::nothrow) EntryRemovedEvent;
							break;
						case B_ENTRY_MOVED:
							event = new(std::nothrow) EntryMovedEvent;
							break;
					}
				}
				if (event) {
					event->queryHandler = this;
					AcquireReference();
					if (event->Init(message) == B_OK)
						fListener->ProcessNodeMonitoringEvent(event);
					else
						delete event;
				}
				break;
			}
			default:
				BHandler::MessageReceived(message);
		}
	}

	virtual void QueryHandleClosed(QueryHandle* handle)
	{
		BLooper* looper = Looper();
		if (looper && looper->Lock()) {
			looper->RemoveHandler(this);
			looper->Unlock();
		}
		handle->SetQueryListener(NULL);
		ReleaseReference();
	}

private:
	NodeMonitorListener*	fListener;
	QueryDomain*			fQueryDomain;
	QueryHandle*			fHandle;
};

// VolumeMap
struct VolumeManager::VolumeMap : HashMap<HashKey32<dev_t>, Volume*> {
};

// ClientVolumeMap
struct VolumeManager::ClientVolumeMap
	: HashMap<HashKey32<int32>, ClientVolume*> {
};

// private BeOS syscalls to set the FD and node monitor slot limits
extern "C" int _kset_fd_limit_(int num);
extern "C" int _kset_mon_limit_(int num);


// EntryCreatedEventMap
struct VolumeManager::EntryCreatedEventMap
	: HashMap<EntryRef, EntryCreatedEvent*> {
};

// EntryRemovedEventMap
struct VolumeManager::EntryRemovedEventMap
	: HashMap<EntryRef, EntryRemovedEvent*> {
};

// EntryMovedEventKey
struct EntryMovedEventKey : public EntryRef {
	EntryMovedEventKey()
	{
	}

	EntryMovedEventKey(dev_t volumeID, ino_t fromDirectory,
		const char* fromName, ino_t toDirectory, const char* toName)
		: EntryRef(volumeID, fromDirectory, fromName),
		  toDirectory(toDirectory),
		  toName(toName)
	{

	}

	uint32 GetHashCode() const
	{
		uint32 hash = EntryRef::GetHashCode();
		hash = 17 * hash + (uint32)(toDirectory >> 32);
		hash = 17 * hash + (uint32)toDirectory;
		hash = 17 * hash + string_hash(toName.GetString());
		return hash;
	}

	bool operator==(const EntryMovedEventKey& other) const
	{
		return (*(const EntryRef*)this) == other
			&& toDirectory == other.toDirectory
			&& toName == other.toName;
	}

	bool operator!=(const EntryMovedEventKey& other) const
	{
		return !(*this == other);
	}

	ino_t		toDirectory;
	HashString	toName;
};

// EntryMovedEventMap
struct VolumeManager::EntryMovedEventMap : HashMap<EntryRef, EntryMovedEvent*> {
};

// NodeStatChangedEventMap
struct VolumeManager::NodeStatChangedEventMap
	: HashMap<NodeRef, StatChangedEvent*> {
};

typedef EntryRef AttributeRef;

// NodeAttributeChangedEventMap
struct VolumeManager::NodeAttributeChangedEventMap
	: HashMap<AttributeRef, AttributeChangedEvent*> {
};


// #pragma mark -

// constructor
VolumeManager::VolumeManager()
	: fLock("volume manager"),
	  fVolumes(NULL),
	  fRootVolume(NULL),
	  fClientVolumes(NULL),
	  fNodeMonitor(NULL),
	  fNodeMonitoringProcessor(-1),
	  fNodeMonitoringEvents(),
	  fRecentNodeMonitoringEvents(),
	  fEntryCreatedEvents(NULL),
	  fEntryRemovedEvents(NULL),
	  fEntryMovedEvents(NULL),
	  fNodeStatChangedEvents(NULL),
	  fNodeAttributeChangedEvents(NULL),
	  fRevision(0),
	  fTerminating(false)
{
}

// destructor
VolumeManager::~VolumeManager()
{
	// terminate the node monitor and the node monitoring processor
	fTerminating = true;
	if (fNodeMonitor && fNodeMonitor->Lock())
		fNodeMonitor->Quit();
	fNodeMonitoringEvents.Close(true);
	if (fNodeMonitoringProcessor >= 0) {
		int32 result;
		wait_for_thread(fNodeMonitoringProcessor, &result);
	}

	// delete all events
	// entry created events
	for (EntryCreatedEventMap::Iterator it = fEntryCreatedEvents->GetIterator();
		 it.HasNext();) {
		it.Next().value->ReleaseReference();
	}
	delete fEntryCreatedEvents;

	// entry removed events
	for (EntryRemovedEventMap::Iterator it = fEntryRemovedEvents->GetIterator();
		 it.HasNext();) {
		it.Next().value->ReleaseReference();
	}
	delete fEntryRemovedEvents;

	// entry moved events
	for (EntryMovedEventMap::Iterator it = fEntryMovedEvents->GetIterator();
		 it.HasNext();) {
		it.Next().value->ReleaseReference();
	}
	delete fEntryMovedEvents;

	// stat changed events
	for (NodeStatChangedEventMap::Iterator it
			= fNodeStatChangedEvents->GetIterator();
		 it.HasNext();) {
		it.Next().value->ReleaseReference();
	}
	delete fNodeStatChangedEvents;

	// attribute changed events
	for (NodeAttributeChangedEventMap::Iterator it
			= fNodeAttributeChangedEvents->GetIterator();
		 it.HasNext();) {
		it.Next().value->ReleaseReference();
	}
	delete fNodeAttributeChangedEvents;

	delete fClientVolumes;

	// delete the volumes
	for (VolumeMap::Iterator it = fVolumes->GetIterator(); it.HasNext();) {
		Volume* volume = it.Next().value;
		delete volume;
	}
	delete fVolumes;
}

// Init
status_t
VolumeManager::Init()
{
	// check node monitoring message queue
	status_t error = fNodeMonitoringEvents.InitCheck();
	if (error != B_OK)
		return error;

	// entry created event map
	fEntryCreatedEvents = new(std::nothrow) EntryCreatedEventMap;
	if (!fEntryCreatedEvents)
		return B_NO_MEMORY;

	// entry removed event map
	fEntryRemovedEvents = new(std::nothrow) EntryRemovedEventMap;
	if (!fEntryRemovedEvents)
		return B_NO_MEMORY;

	// entry moved event map
	fEntryMovedEvents = new(std::nothrow) EntryMovedEventMap;
	if (!fEntryMovedEvents)
		return B_NO_MEMORY;

	// node stat changed event map
	fNodeStatChangedEvents = new(std::nothrow) NodeStatChangedEventMap;
	if (!fNodeStatChangedEvents)
		return B_NO_MEMORY;

	// node attribute changed event map
	fNodeAttributeChangedEvents = new(std::nothrow) NodeAttributeChangedEventMap;
	if (!fNodeAttributeChangedEvents)
		return B_NO_MEMORY;

	// create the node monitor
	fNodeMonitor = new(std::nothrow) NodeMonitor(this);
	if (!fNodeMonitor)
		return B_NO_MEMORY;

	// create the volume map
	fVolumes = new(std::nothrow) VolumeMap;
	if (!fVolumes)
		return B_NO_MEMORY;
	if (fVolumes->InitCheck() != B_OK)
		return fVolumes->InitCheck();

	// create the client volume map
	fClientVolumes = new(std::nothrow) ClientVolumeMap;
	if (!fClientVolumes)
		return B_NO_MEMORY;
	if (fClientVolumes->InitCheck() != B_OK)
		return fClientVolumes->InitCheck();

	// start the node monitor
	thread_id monitorThread = fNodeMonitor->Run();
	if (monitorThread < 0) {
		delete fNodeMonitor;
		fNodeMonitor = NULL;
		return monitorThread;
	}

	// create all volumes
	int32 cookie = 0;
	dev_t volumeID;
	while ((volumeID = next_dev(&cookie)) >= 0)
		_AddVolume(volumeID);

	// get the root volume
	volumeID = dev_for_path("/");
	if (volumeID < 0)
		return volumeID;
	fRootVolume = GetVolume(volumeID, true);
	if (!fRootVolume)
		return B_ERROR;

	// spawn the node monitoring message processor
	fNodeMonitoringProcessor = spawn_thread(&_NodeMonitoringProcessorEntry,
		"node monitoring processor", B_NORMAL_PRIORITY, this);
	if (fNodeMonitoringProcessor < 0)
		return fNodeMonitoringProcessor;
	resume_thread(fNodeMonitoringProcessor);

	return B_OK;
}

// GetRootVolume
Volume*
VolumeManager::GetRootVolume() const
{
	return fRootVolume;
}

// AddClientVolume
status_t
VolumeManager::AddClientVolume(ClientVolume* clientVolume)
{
	if (!clientVolume)
		return B_BAD_VALUE;

	return fClientVolumes->Put(clientVolume->GetID(), clientVolume);
}

// RemoveClientVolume
void
VolumeManager::RemoveClientVolume(ClientVolume* clientVolume)
{
	if (!clientVolume)
		return;

	fClientVolumes->Remove(clientVolume->GetID());
}

// CreateDefault
status_t
VolumeManager::CreateDefault()
{
	if (sManager)
		return B_OK;

	VolumeManager* manager = new(std::nothrow) VolumeManager;
	if (!manager)
		return B_NO_MEMORY;

	status_t error = manager->Init();
	if (error != B_OK) {
		delete manager;
		return error;
	}

	sManager = manager;
	return B_OK;
}

// DeleteDefault
void
VolumeManager::DeleteDefault()
{
	if (sManager) {
		delete sManager;
		sManager = NULL;
	}
}

// GetDefault
VolumeManager*
VolumeManager::GetDefault()
{
	return sManager;
}

// Lock
bool
VolumeManager::Lock()
{
	bool alreadyLocked = fLock.IsLocked();

	bool success = fLock.Lock();

	// If locking was successful and we didn't have a lock before, we increment
	// the revision.
	if (success && !alreadyLocked)
		fRevision++;

	return success;
}

// Unlock
void
VolumeManager::Unlock()
{
	return fLock.Unlock();
}

// GetRevision
int64
VolumeManager::GetRevision() const
{
	return fRevision;
}

// GetVolume
Volume*
VolumeManager::GetVolume(dev_t volumeID, bool add)
{
	Volume* volume = fVolumes->Get(volumeID);
	if (!volume && add)
		_AddVolume(volumeID, &volume);

	return volume;
}


// #pragma mark -

// AddNode
status_t
VolumeManager::AddNode(Node* node)
{
	if (!node || !node->GetVolume())
		return B_BAD_VALUE;

	status_t error = node->GetVolume()->AddNode(node);

	// start watching the node
	if (error == B_OK)
		fNodeMonitor->StartWatching(node->GetNodeRef());

	return error;
}

// RemoveNode
void
VolumeManager::RemoveNode(Node* node)
{
	if (!node)
		return;

	// if the node is a directory, we remove all its entries first
	if (Directory* directory = dynamic_cast<Directory*>(node)) {
		while (Entry* entry = directory->GetFirstEntry()) {
			RemoveEntry(entry);
			delete entry;
		}
	}

	// remove all referring entries
	while (Entry* entry = node->GetFirstReferringEntry())
		RemoveEntry(entry);

	// remove the node from the volume
	if (node->GetVolume())
		node->GetVolume()->RemoveNode(node);

	// stop watching the node
	fNodeMonitor->StopWatching(node->GetNodeRef());
}

// GetNode
Node*
VolumeManager::GetNode(dev_t volumeID, ino_t nodeID)
{
	if (Volume* volume = GetVolume(volumeID))
		return volume->GetNode(nodeID);
	return NULL;
}

// LoadNode
status_t
VolumeManager::LoadNode(const struct stat& st, Node** _node)
{
	Node* node = GetNode(st.st_dev, st.st_ino);
	if (!node) {
		// node not known yet: create it

		// get the volume
		Volume* volume = GetVolume(st.st_dev, true);
		if (!volume)
			return B_BAD_VALUE;

		// create the node
		if (S_ISDIR(st.st_mode))
			node = new(std::nothrow) Directory(volume, st);
		else
			node = new(std::nothrow) Node(volume, st);
		if (!node)
			return B_NO_MEMORY;

		// add it
		status_t error = AddNode(node);
		if (error != B_OK) {
			delete node;
			return error;
		}
	}

	if (_node)
		*_node = node;
	return B_OK;
}


// #pragma mark -

// GetDirectory
Directory*
VolumeManager::GetDirectory(dev_t volumeID, ino_t nodeID)
{
	return dynamic_cast<Directory*>(GetNode(volumeID, nodeID));
}

// GetRootDirectory
Directory*
VolumeManager::GetRootDirectory() const
{
	return (fRootVolume ? fRootVolume->GetRootDirectory() : NULL);
}

// GetParentDirectory
Directory*
VolumeManager::GetParentDirectory(Directory* directory)
{
	if (!directory)
		return NULL;

	// get ".." entry
	Entry* parentEntry;
	if (LoadEntry(directory->GetVolumeID(), directory->GetID(), "..", true,
			&parentEntry) != B_OK) {
		return NULL;
	}

	return dynamic_cast<Directory*>(parentEntry->GetNode());
}

// LoadDirectory
status_t
VolumeManager::LoadDirectory(dev_t volumeID, ino_t directoryID,
	Directory** _directory)
{
	// try to get the node
	Node* node = GetNode(volumeID, directoryID);
	bool newNode = false;
	if (!node) {
		// directory not yet loaded: stat it
		NoAllocEntryRef entryRef(volumeID, directoryID, ".");
		struct stat st;
		BEntry bEntry;
		status_t error = FDManager::SetEntry(&bEntry, &entryRef);
		if (error == B_OK)
			error = bEntry.GetStat(&st);
		if (error != B_OK)
			return error;

		// load the node
		error = LoadNode(st, &node);
		if (error != B_OK)
			return error;

		newNode = true;
	}

	// check, if the node is a directory
	Directory* directory = dynamic_cast<Directory*>(node);
	if (!directory)
		return B_NOT_A_DIRECTORY;

	if (newNode)
		CompletePathToRoot(directory);

	if (_directory)
		*_directory = directory;
	return B_OK;
}


// #pragma mark -

// AddEntry
status_t
VolumeManager::AddEntry(Entry* entry)
{
	if (!entry || !entry->GetVolume() || !entry->GetDirectory()
		|| ! entry->GetNode()) {
		return B_BAD_VALUE;
	}

	// add the entry to the volume
	status_t error = entry->GetVolume()->AddEntry(entry);
	if (error != B_OK)
		return error;

	// add the entry to its directory and node
	entry->GetDirectory()->AddEntry(entry);
	entry->GetNode()->AddReferringEntry(entry);

//PRINT(("VolumeManager::AddEntry(): %ld, %lld, `%s', dir: %p, "
//"entry count: %ld\n", entry->GetVolumeID(), entry->GetDirectoryID(),
//entry->GetName(), entry->GetDirectory(),
//entry->GetDirectory()->CountEntries()));

	return B_OK;
}

// RemoveEntry
void
VolumeManager::RemoveEntry(Entry* entry)
{
	if (entry) {
		// remove the entry from the volume
		if (entry->GetVolume())
			entry->GetVolume()->RemoveEntry(entry);

		// remove the entry from the directory and its node
		entry->GetDirectory()->RemoveEntry(entry);
		entry->GetNode()->RemoveReferringEntry(entry);

//PRINT(("VolumeManager::RemoveEntry(): %ld, %lld, `%s', dir: %p, "
//"entry count: %ld\n", entry->GetVolumeID(), entry->GetDirectoryID(),
//entry->GetName(), entry->GetDirectory(),
//entry->GetDirectory()->CountEntries()));
	}
}

// DeleteEntry
void
VolumeManager::DeleteEntry(Entry* entry, bool keepNode)
{
	if (!entry)
		return;

	Node* node = entry->GetNode();

	// remove the entry
	RemoveEntry(entry);
	delete entry;

	// remove the node, if it doesn't have any more actual referring entries
	if (!keepNode && !node->GetActualReferringEntry()) {
		RemoveNode(node);
		if (node != node->GetVolume()->GetRootDirectory())
			delete node;
	}
}

// GetEntry
Entry*
VolumeManager::GetEntry(dev_t volumeID, ino_t directoryID, const char* name)
{
	if (Volume* volume = GetVolume(volumeID))
		return volume->GetEntry(directoryID, name);
	return NULL;
}

// GetEntry
Entry*
VolumeManager::GetEntry(const entry_ref& ref)
{
	return GetEntry(ref.device, ref.directory, ref.name);
}

// LoadEntry
status_t
VolumeManager::LoadEntry(dev_t volumeID, ino_t directoryID, const char* name,
	bool loadDir, Entry** _entry)
{
	Entry* entry = GetEntry(volumeID, directoryID, name);
	if (!entry) {
		// entry not known yet: create it
		PRINT("VolumeManager::LoadEntry(%" B_PRIdDEV ", "
			"%" B_PRIdINO ", `%s')\n", volumeID, directoryID, name);

		// get the volume
		Volume* volume = GetVolume(volumeID, true);
		if (!volume)
			return B_BAD_VALUE;

		// get the directory
		status_t error = B_OK;
		Directory* directory = GetDirectory(volumeID, directoryID);
		if (!directory) {
			if (!loadDir)
				return B_ENTRY_NOT_FOUND;

//PRINT(("  loading directory...\n"));
			// load the directory
			error = LoadDirectory(volumeID, directoryID, &directory);
			if (error != B_OK)
				return error;
		}

//PRINT(("  opening BNode...\n"));
		// stat the entry
		NoAllocEntryRef entryRef(volumeID, directoryID, name);
		struct stat st;
		BNode bNode;
		error = bNode.SetTo(&entryRef);
//PRINT(("  stat()ing BNode...\n"));
		if (error == B_OK)
			error = bNode.GetStat(&st);
		if (error != B_OK)
			return error;

//PRINT(("  loading node...\n"));
		// load the node
		Node* node;
		error = LoadNode(st, &node);
		if (error != B_OK)
			return error;

//PRINT(("  creating and adding entry...\n"));
		// create the entry
		entry = new(std::nothrow) Entry(volume, directory, name, node);
		if (!entry)
			return B_NO_MEMORY;

		// add it
		error = AddEntry(entry);
		if (error != B_OK) {
			delete entry;
			return error;
		}
//PRINT(("  adding entry done\n"));
	}

	if (_entry)
		*_entry = entry;
	return B_OK;
}


// #pragma mark -

// OpenQuery
status_t
VolumeManager::OpenQuery(QueryDomain* queryDomain, const char* queryString,
	uint32 flags, port_id remotePort, int32 remoteToken, QueryHandle** handle)
{
	if (!queryDomain || !queryString || !handle)
		return B_BAD_VALUE;
	bool liveQuery = (flags & B_LIVE_QUERY);
	PRINT("VolumeManager::OpenQuery(%p, \"%s\", 0x%" B_PRIx32 ", "
		"%" B_PRId32 ", %" B_PRId32 ")\n",
		queryDomain, queryString, flags, remotePort, remoteToken);

	// allocate the handle
	QueryHandle* queryHandle = new(std::nothrow) QueryHandle(remotePort,
		remoteToken);
	if (!queryHandle)
		return B_NO_MEMORY;
	ObjectDeleter<QueryHandle> handleDeleter(queryHandle);

	// allocate a query handler, if this is a live query
	QueryHandler* queryHandler = NULL;
	if (liveQuery) {
		queryHandler = new(std::nothrow) QueryHandler(this, queryDomain,
			queryHandle);
		if (!queryHandler)
			return B_NO_MEMORY;

		fNodeMonitor->Lock();
		fNodeMonitor->AddHandler(queryHandler);
		fNodeMonitor->Unlock();
		queryHandle->SetQueryListener(queryHandler);
	}

	// iterate through the volumes and create a query for each one
	// supporting queries
	for (VolumeMap::Iterator it = fVolumes->GetIterator(); it.HasNext();) {
		Volume* volume = it.Next().value;
		if (!volume->KnowsQuery())
			continue;

		// The volume should either be contained by the client volume or
		// the other way around. Otherwise they are located in different
		// branches of the FS tree and don't have common nodes.
		if (!queryDomain->QueryDomainIntersectsWith(volume))
			continue;
		PRINT("VolumeManager::OpenQuery(): adding Query for volume "
			"%" B_PRIdDEV "\n", volume->GetID());

		// create the query for this volume
		BVolume bVolume(volume->GetID());
		Query* query = new(std::nothrow) Query;
		if (!query)
			return B_NO_MEMORY;

		// init the query
		ObjectDeleter<Query> queryDeleter(query);
		status_t error = query->SetVolume(&bVolume);
		if (error != B_OK)
			return error;
		error = query->SetPredicate(queryString);
		if (error != B_OK)
			return error;
		if (liveQuery) {
			error = query->SetTarget(queryHandler);
			if (error != B_OK)
				return error;
		}

		// fetch
		error = query->Fetch();
		if (error != B_OK)
			return error;

		queryHandle->AddQuery(query);
		queryDeleter.Detach();
	}

	*handle = queryHandle;
	handleDeleter.Detach();
	return B_OK;
}

// CompletePathToRoot
status_t
VolumeManager::CompletePathToRoot(Directory* directory)
{
	if (!directory)
		return B_BAD_VALUE;

	while (directory != GetRootDirectory()) {
		// if the dir has a valid entry referring to it, we've nothing to do
		if (directory->GetActualReferringEntry())
			return B_OK;

		// get a proper entry_ref
		BEntry bEntry;
		entry_ref entryRef(directory->GetVolumeID(), directory->GetID(), ".");
		status_t error = FDManager::SetEntry(&bEntry, &entryRef);
		if (error == B_OK)
			error = bEntry.GetRef(&entryRef);
		if (error != B_OK)
			return error;

		// if the entry is already loaded, we're done
		if (GetEntry(entryRef))
			return B_OK;

		// the entry is not yet known -- load it
		Entry* entry;
		error = LoadEntry(entryRef.device, entryRef.directory, entryRef.name,
			true, &entry);
		if (error != B_OK)
			return error;

		// get the entry's parent dir and enter the next round
		directory = entry->GetDirectory();
	}

	return B_OK;
}

// GetPath
status_t
VolumeManager::GetPath(Entry* entry, Path* path)
{
	// get directory path
	status_t error = GetPath(entry->GetDirectory(), path);
	if (error != B_OK)
		return error;

	// append the entry name
	return path->Append(entry->GetName());
}

// GetPath
status_t
VolumeManager::GetPath(Node* node, Path* path)
{
	if (node == GetRootDirectory())
		return path->SetTo("/");

	// get an entry referring to the node
	Entry* entry = node->GetActualReferringEntry();
	if (!entry) {
		// if the node is a directory, we complete the path to the root and
		// try again
		if (Directory* directory = dynamic_cast<Directory*>(node)) {
			CompletePathToRoot(directory);
			entry = node->GetActualReferringEntry();
		}

		if (!entry)
			return B_ERROR;
	}

	return GetPath(entry, path);
}

// DirectoryContains
bool
VolumeManager::DirectoryContains(Directory* directory, Entry* entry)
{
	if (!directory || !entry)
		return false;

	return DirectoryContains(directory, entry->GetDirectory(), true);
}

// DirectoryContains
bool
VolumeManager::DirectoryContains(Directory* directory, Directory* descendant,
	bool reflexive)
{
	if (!directory || !descendant)
		return false;

	// a directory contains itself, just as defined by the caller
	if (directory == descendant)
		return reflexive;

	// if the directory is the root directory, it contains everything
	Directory* rootDir = GetRootDirectory();
	if (directory == rootDir)
		return true;

	// recursively get the descendant's parent dir until reaching the root dir
	// or the given dir
	while (descendant != rootDir) {
		descendant = GetParentDirectory(descendant);
		if (!descendant)
			return false;

		if (descendant == directory)
			return true;
	}

	return false;
}

// DirectoryContains
bool
VolumeManager::DirectoryContains(Directory* directory, Node* descendant,
	bool reflexive)
{
	if (!directory || !descendant)
		return false;

	// if the node is a directory, let the other version do the job
	if (Directory* dir = dynamic_cast<Directory*>(descendant))
		return DirectoryContains(directory, dir, reflexive);

	// iterate through the referring entries and check, if the directory
	// contains any of them
	for (Entry* entry = descendant->GetFirstReferringEntry();
		 entry;
		 entry = descendant->GetNextReferringEntry(entry)) {
		if (DirectoryContains(directory, entry))
			return true;
	}

	return false;
}


// #pragma mark -

// ProcessNodeMonitoringEvent
void
VolumeManager::ProcessNodeMonitoringEvent(NodeMonitoringEvent* event)
{
	if (fNodeMonitoringEvents.Push(event) != B_OK)
		delete event;
}

// _AddVolume
status_t
VolumeManager::_AddVolume(dev_t volumeID, Volume** _volume)
{
	if (GetVolume(volumeID))
		return B_OK;

	// create the volume
	Volume* volume = new(std::nothrow) Volume(volumeID);
	if (!volume)
		RETURN_ERROR(B_NO_MEMORY);
	ObjectDeleter<Volume> volumeDeleter(volume);
	status_t error = volume->Init();
	if (error != B_OK)
		RETURN_ERROR(error);

	// add it
	error = fVolumes->Put(volumeID, volume);
	if (error != B_OK)
		RETURN_ERROR(error);

	// add the root node
	error = AddNode(volume->GetRootDirectory());
	if (error != B_OK) {
		fVolumes->Remove(volumeID);
		RETURN_ERROR(error);
	}

	// complete the root dir path
	CompletePathToRoot(volume->GetRootDirectory());

	volumeDeleter.Detach();
	if (_volume)
		*_volume = volume;
	return B_OK;
}

// _EntryCreated
void
VolumeManager::_EntryCreated(EntryCreatedEvent* event)
{
	// get the directory
	Directory* directory = GetDirectory(event->volumeID, event->directoryID);
	if (!directory)
		return;

	// check, if there is an earlier similar event
	bool notify = true;
	NoAllocEntryRef ref(event->volumeID, event->directoryID,
		event->name.GetString());
	EntryCreatedEvent* oldEvent = fEntryCreatedEvents->Get(ref);

	// remove the old event
	if (oldEvent) {
		fEntryCreatedEvents->Remove(ref);
		fRecentNodeMonitoringEvents.Remove(oldEvent);
		notify = !_IsRecentEvent(oldEvent);
		oldEvent->ReleaseReference();
	}

	// add the new event
	if (fEntryCreatedEvents->Put(ref, event) == B_OK) {
		fRecentNodeMonitoringEvents.Insert(event);
		event->AcquireReference();
	}

	// if the directory is complete or at least has iterators attached to it,
	// we load the entry
	if (directory->IsComplete() || directory->HasDirIterators()) {
		Entry* entry;
		LoadEntry(ref.device, ref.directory, ref.name, false, &entry);
	}

	// send notifications
	if (notify) {
		for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator();
			 it.HasNext();) {
			ClientVolume* clientVolume = it.Next().value;
			if (DirectoryContains(clientVolume->GetRootDirectory(), directory,
				true)) {
				clientVolume->ProcessNodeMonitoringEvent(event);
			}
		}
	}
}

// _EntryRemoved
void
VolumeManager::_EntryRemoved(EntryRemovedEvent* event, bool keepNode)
{
	// get node and directory
	Node* node = GetNode(event->nodeVolumeID, event->nodeID);
	Directory* directory = GetDirectory(event->volumeID, event->directoryID);
	if (!directory)
		return;

	// find the entry
	Entry* entry = NULL;
	if (node) {
		if (event->name.GetLength() == 0) {
			for (entry = node->GetFirstReferringEntry();
				 entry;
				 entry = node->GetNextReferringEntry(entry)) {
				if (!entry->Exists()) {
					event->name.SetTo(entry->GetName());
					break;
				}
			}
		} else {
			entry = GetEntry(directory->GetVolumeID(), directory->GetID(),
				event->name.GetString());
		}
	}

	// check, if there is an earlier similar event
	bool notify = true;
	NoAllocEntryRef ref(event->volumeID, event->directoryID,
		event->name.GetString());
	EntryRemovedEvent* oldEvent = fEntryRemovedEvents->Get(ref);
		// TODO: Under BeOS R5 the entry name is not encoded in the
		// "entry removed" node monitoring message. If we have seen the entry
		// before, we can get the entry nevertheless (see above). Usually we
		// get 2 "entry removed" events: One for watching the directory and one
		// for watching the node. After the first one has been processed, we've
		// forgotten everything about the entry and we won't be able to find out
		// the entry's name for the second one. Hence we will never find the
		// previous event in the fEntryRemovedEvents map. We should probably
		// fall back to using a NodeRef as key under BeOS R5.

	// remove the old event
	if (oldEvent) {
		fEntryRemovedEvents->Remove(ref);
		fRecentNodeMonitoringEvents.Remove(oldEvent);
		notify = !_IsRecentEvent(oldEvent);
		oldEvent->ReleaseReference();
	}

	// add the new event
	if (fEntryRemovedEvents->Put(ref, event) == B_OK) {
		fRecentNodeMonitoringEvents.Insert(event);
		event->AcquireReference();
	}

	// remove the entry
	if (entry) {
		RemoveEntry(entry);
		delete entry;
	}

	// remove the node, if it doesn't have any more actual referring entries
	if (node && !keepNode && !node->GetActualReferringEntry()) {
		RemoveNode(node);
		if (node != node->GetVolume()->GetRootDirectory())
			delete node;
	}

	// send notifications
	if (notify) {
		NodeRef nodeRef(event->nodeVolumeID, event->nodeID);
		for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator();
			 it.HasNext();) {
			// We send a notification, if the client volume contains the entry,
			// but also, if the removed entry refers to the client volume's
			// root. The client connection has a special handling for this
			// case.
			ClientVolume* clientVolume = it.Next().value;
			Directory* rootDir = clientVolume->GetRootDirectory();
			if (DirectoryContains(rootDir, directory, true)
				|| clientVolume->GetRootNodeRef() == nodeRef) {
				clientVolume->ProcessNodeMonitoringEvent(event);
			}
		}
	}
}

// _EntryMoved
void
VolumeManager::_EntryMoved(EntryMovedEvent* event)
{
	_CheckVolumeRootMoved(event);

	Directory* fromDirectory
		= GetDirectory(event->volumeID, event->fromDirectoryID);
	Directory* toDirectory
		= GetDirectory(event->volumeID, event->toDirectoryID);
	Node* node = GetNode(event->nodeVolumeID, event->nodeID);

	// we should at least have one of the directories
	if (!fromDirectory && !toDirectory)
		return;

	// find the old entry
	Entry* oldEntry = NULL;
	if (node) {
		if (event->fromName.GetLength() == 0) {
			for (oldEntry = node->GetFirstReferringEntry();
				 oldEntry;
				 oldEntry = node->GetNextReferringEntry(oldEntry)) {
				if (!oldEntry->Exists()) {
					event->fromName.SetTo(oldEntry->GetName());
					break;
				}
			}
		} else {
			oldEntry = GetEntry(event->volumeID, event->fromDirectoryID,
				event->fromName.GetString());
		}
	}

	// check, if there is an earlier similar event
	bool notify = true;
	if (event->fromName.GetLength() > 0) {
		EntryMovedEventKey key(event->volumeID, event->fromDirectoryID,
			event->fromName.GetString(), event->toDirectoryID,
			event->toName.GetString());
		EntryMovedEvent* oldEvent = fEntryMovedEvents->Get(key);

		// remove the old event
		if (oldEvent) {
			fEntryMovedEvents->Remove(key);
			fRecentNodeMonitoringEvents.Remove(oldEvent);
			notify = !_IsRecentEvent(oldEvent);
			oldEvent->ReleaseReference();
		}

		// add the new event
		if (fEntryMovedEvents->Put(key, event) == B_OK) {
			fRecentNodeMonitoringEvents.Insert(event);
			event->AcquireReference();
		}
	}

	// remove the old entry
	if (oldEntry) {
		RemoveEntry(oldEntry);
		delete oldEntry;
	}

	// If the to directory is complete or at least has iterators attached to it,
	// we load the new entry. We also load it, if the node is the root of a
	// volume.
	if (toDirectory
		&& (toDirectory->IsComplete() || toDirectory->HasDirIterators()
			|| (node && node == node->GetVolume()->GetRootDirectory()))) {
		Entry* newEntry;
		LoadEntry(event->volumeID, event->toDirectoryID,
			event->toName.GetString(), false, &newEntry);
	}

	// remove the node, if it doesn't have any more actual referring entries
	if (node && !node->GetActualReferringEntry()) {
		RemoveNode(node);
		if (node != node->GetVolume()->GetRootDirectory())
			delete node;
	}

	// send notifications
	if (notify) {
		for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator();
			 it.HasNext();) {
			ClientVolume* clientVolume = it.Next().value;

			// check, if it contains the from/to directories
			Directory* rootDir = clientVolume->GetRootDirectory();
			bool containsFrom = DirectoryContains(rootDir, fromDirectory, true);
			bool containsTo = DirectoryContains(rootDir, toDirectory, true);

			if (containsFrom) {
				if (containsTo) {
					// contains source and target dir
					clientVolume->ProcessNodeMonitoringEvent(event);
				} else {
					// contains only the source dir: generate an "entry removed"
					// event
					EntryRemovedEvent *removedEvent
						= new(std::nothrow) EntryRemovedEvent;
					if (!removedEvent)
						continue;
					removedEvent->opcode = B_ENTRY_REMOVED;
					removedEvent->time = event->time;
					removedEvent->volumeID = event->volumeID;
					removedEvent->directoryID = event->fromDirectoryID;
					removedEvent->nodeVolumeID = event->nodeVolumeID;
					removedEvent->nodeID = event->nodeID;
					if (event->fromName.GetLength() > 0)
						removedEvent->name = event->fromName;
					clientVolume->ProcessNodeMonitoringEvent(removedEvent);
					removedEvent->ReleaseReference();
				}
			} else if (containsTo) {
				// contains only the target directory: generate an
				// "entry created" event
				EntryCreatedEvent *createdEvent
					= new(std::nothrow) EntryCreatedEvent;
				if (!createdEvent)
					continue;
				createdEvent->opcode = B_ENTRY_CREATED;
				createdEvent->time = event->time;
				createdEvent->volumeID = event->volumeID;
				createdEvent->directoryID = event->toDirectoryID;
				createdEvent->name = event->toName;
				clientVolume->ProcessNodeMonitoringEvent(createdEvent);
				createdEvent->ReleaseReference();
			}
		}
	}
}

// _NodeStatChanged
void
VolumeManager::_NodeStatChanged(StatChangedEvent* event)
{
	// get the node
	Node* node = GetNode(event->volumeID, event->nodeID);
	if (!node)
		return;

	// check, if there is an earlier similar event
	bool notify = true;
	NodeRef ref(event->volumeID, event->nodeID);
	StatChangedEvent* oldEvent = fNodeStatChangedEvents->Get(ref);

	// remove the old event
	if (oldEvent) {
		fNodeStatChangedEvents->Remove(ref);
		fRecentNodeMonitoringEvents.Remove(oldEvent);
		notify = !_IsRecentEvent(oldEvent);
		oldEvent->ReleaseReference();
	}

	// add the new event
	if (fNodeStatChangedEvents->Put(ref, event) == B_OK) {
		fRecentNodeMonitoringEvents.Insert(event);
		event->AcquireReference();
	}

	if (notify) {
		// update the cached node stat
		node->UpdateStat();

		// send notifications
		for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator();
			 it.HasNext();) {
			ClientVolume* clientVolume = it.Next().value;
			if (DirectoryContains(clientVolume->GetRootDirectory(), node, true))
				clientVolume->ProcessNodeMonitoringEvent(event);
		}
	}
}

// _NodeAttributeChanged
void
VolumeManager::_NodeAttributeChanged(AttributeChangedEvent* event)
{
	// get the node
	Node* node = GetNode(event->volumeID, event->nodeID);
	if (!node)
		return;

	// check, if there is an earlier similar event
	bool notify = true;
	AttributeRef ref(event->volumeID, event->nodeID,
		event->attribute.GetString());
	AttributeChangedEvent* oldEvent = fNodeAttributeChangedEvents->Get(ref);

	// remove the old event
	if (oldEvent) {
		fNodeAttributeChangedEvents->Remove(ref);
		fRecentNodeMonitoringEvents.Remove(oldEvent);
		notify = !_IsRecentEvent(oldEvent);
		oldEvent->ReleaseReference();
	}

	// add the new event
	if (fNodeAttributeChangedEvents->Put(ref, event) == B_OK) {
		fRecentNodeMonitoringEvents.Insert(event);
		event->AcquireReference();
	}

	// send notifications
	if (notify) {
		for (ClientVolumeMap::Iterator it = fClientVolumes->GetIterator();
			 it.HasNext();) {
			ClientVolume* clientVolume = it.Next().value;
			if (DirectoryContains(clientVolume->GetRootDirectory(), node, true))
				clientVolume->ProcessNodeMonitoringEvent(event);
		}
	}
}

// _VolumeMounted
void
VolumeManager::_VolumeMounted(VolumeMountedEvent* event)
{
	entry_ref rootRef;
	bool rootRefInitialized = false;

	// remove the entry referring to the covered directory
	Directory* coveredDirectory = GetDirectory(event->volumeID,
		event->directoryID);
	if (coveredDirectory) {
		if (Entry* entry = coveredDirectory->GetActualReferringEntry()) {
			// get an entry for later
			rootRef = entry->GetEntryRef();
			rootRefInitialized = true;

			// send the "entry removed" event
			EntryRemovedEvent* event;
			if (_GenerateEntryRemovedEvent(entry, system_time(),
					&event) == B_OK) {
				_EntryRemoved(event, true);
				event->ReleaseReference();
			} else {
				RemoveEntry(entry);
				delete entry;
			}
		}
	}

	// add the volume
	_AddVolume(event->newVolumeID);

	// generate an "entry created" event for the root dir entry
	if (rootRefInitialized)
		_GenerateEntryCreatedEvent(rootRef, event->time);
}

// _VolumeUnmounted
void
VolumeManager::_VolumeUnmounted(VolumeUnmountedEvent* event)
{
	// get the volume
	Volume* volume = GetVolume(event->volumeID);
	if (!volume)
		return;

	entry_ref rootRef;
	bool rootRefInitialized = false;

	// remove all actual entries referring to the root directory (should only
	// be one)
	if (Directory* rootDir = volume->GetRootDirectory()) {
		// get an entry ref for the root dir
		if (Entry* entry = rootDir->GetActualReferringEntry()) {
			rootRef = entry->GetEntryRef();
			rootRefInitialized = true;
		}

		Entry* entry = rootDir->GetFirstReferringEntry();
		while (entry) {
			Entry* nextEntry = rootDir->GetNextReferringEntry(entry);

			if (entry->IsActualEntry()) {
				EntryRemovedEvent* removedEvent;
				if (_GenerateEntryRemovedEvent(entry, event->time,
						&removedEvent) == B_OK) {
					_EntryRemoved(removedEvent, true);
					removedEvent->ReleaseReference();
				} else {
					RemoveEntry(entry);
					delete entry;
				}
			}

			entry = nextEntry;
		}
	}

	// remove all entries of the volume
	while (Entry* entry = volume->GetFirstEntry()) {
		bool remove = true;
		if (entry->IsActualEntry()) {
			if (_GenerateEntryRemovedEvent(entry, event->time) != B_OK)
				remove = false;
		}

		if (remove) {
			RemoveEntry(entry);
			delete entry;
		}
	}

	// remove all nodes
	while (Node* node = volume->GetFirstNode()) {
		RemoveNode(node);
		if (node != volume->GetRootDirectory())
			delete node;
	}

	// remove the volume
	fVolumes->Remove(volume->GetID());
	delete volume;

	// generate an "entry created" event for the covered node
	if (rootRefInitialized)
		_GenerateEntryCreatedEvent(rootRef, event->time);
}

// _QueryEntryCreated
void
VolumeManager::_QueryEntryCreated(EntryCreatedEvent* event)
{
	// get the query handler
	QueryHandler* queryHandler
		= dynamic_cast<QueryHandler*>(event->queryHandler);
	if (!queryHandler)
		return;

	// load the entry (just to make sure that it really exists)
	Entry* entry = NULL;
	status_t error = LoadEntry(event->volumeID, event->directoryID,
		event->name.GetString(), true, &entry);
	if (error != B_OK)
		return;

	// get remote port and token
	if (!queryHandler->LockLooper())
		return;

	QueryHandle* queryHandle = queryHandler->GetQueryHandle();
	event->remotePort = queryHandle->GetRemotePort();
	event->remoteToken = queryHandle->GetRemoteToken();
	queryHandler->UnlockLooper();

	// send a notification to the client volume
	queryHandler->GetQueryDomain()->ProcessQueryEvent(event);
}

// _QueryEntryRemoved
void
VolumeManager::_QueryEntryRemoved(EntryRemovedEvent* event)
{
	// get the query handler
	QueryHandler* queryHandler
		= dynamic_cast<QueryHandler*>(event->queryHandler);
	if (!queryHandler)
		return;

	// load the directory (just to make sure that it really exists)
	Directory* directory = NULL;
	status_t error = LoadDirectory(event->volumeID, event->directoryID,
		&directory);
	if (error != B_OK)
		return;

	// get remote port and token
	if (!queryHandler->LockLooper())
		return;
	QueryHandle* queryHandle = queryHandler->GetQueryHandle();
	event->remotePort = queryHandle->GetRemotePort();
	event->remoteToken = queryHandle->GetRemoteToken();
	queryHandler->UnlockLooper();

	// send a notification to the client volume
	queryHandler->GetQueryDomain()->ProcessQueryEvent(event);
}

// _QueryEntryMoved
void
VolumeManager::_QueryEntryMoved(EntryMovedEvent* event)
{
	// we simply split the event into a `removed' and a `created' event

	// allocate the events
	EntryRemovedEvent* removedEvent = new(std::nothrow) EntryRemovedEvent;
	EntryCreatedEvent* createdEvent = new(std::nothrow) EntryCreatedEvent;
	if (!removedEvent || !createdEvent) {
		delete removedEvent;
		delete createdEvent;
		return;
	}

	// init the removed event
	removedEvent->opcode = B_ENTRY_REMOVED;
	removedEvent->time = event->time;
	removedEvent->queryHandler = event->queryHandler;
	removedEvent->queryHandler->AcquireReference();
	removedEvent->volumeID = event->volumeID;
	removedEvent->directoryID = event->fromDirectoryID;
	removedEvent->nodeVolumeID = event->volumeID;
	removedEvent->nodeID = event->nodeID;
	removedEvent->name = event->fromName;

	// init the created event
	createdEvent->opcode = B_ENTRY_CREATED;
	createdEvent->time = event->time;
	createdEvent->queryHandler = event->queryHandler;
	createdEvent->queryHandler->AcquireReference();
	createdEvent->volumeID = event->volumeID;
	createdEvent->directoryID = event->toDirectoryID;
	createdEvent->nodeID = event->nodeID;
	createdEvent->name = event->toName;

	// send them
	_QueryEntryRemoved(removedEvent);
	removedEvent->ReleaseReference();
	_QueryEntryCreated(createdEvent);
	createdEvent->ReleaseReference();
}

// _IsRecentEvent
bool
VolumeManager::_IsRecentEvent(NodeMonitoringEvent* event) const
{
	return (event && system_time() < event->time + kRecentEventLifeTime);
}

// _GenerateEntryCreatedEvent
status_t
VolumeManager::_GenerateEntryCreatedEvent(const entry_ref& ref, bigtime_t time,
	EntryCreatedEvent** _event)
{
	// load the entry
	Entry* entry;
	status_t error = LoadEntry(ref.device, ref.directory, ref.name, true,
		&entry);
	if (error != B_OK)
		return error;

	// create the event
	EntryCreatedEvent* event = new(std::nothrow) EntryCreatedEvent;
	if (!event)
		return B_NO_MEMORY;

	// fill in the fields
	event->opcode = B_ENTRY_CREATED;
	event->time = time;
	event->volumeID = entry->GetVolumeID();
	event->directoryID = entry->GetDirectoryID();
	event->nodeID = entry->GetNode()->GetID();
	event->name.SetTo(entry->GetName());

	if (_event) {
		*_event = event;
	} else {
		_EntryCreated(event);
		event->ReleaseReference();
	}

	return B_OK;
}

// _GenerateEntryRemovedEvent
status_t
VolumeManager::_GenerateEntryRemovedEvent(Entry* entry, bigtime_t time,
	EntryRemovedEvent** _event)
{
	if (!entry)
		return B_BAD_VALUE;

	// create the event
	EntryRemovedEvent* event = new(std::nothrow) EntryRemovedEvent;
	if (!event)
		return B_NO_MEMORY;

	// fill in the fields
	event->opcode = B_ENTRY_REMOVED;
	event->time = time;
	event->volumeID = entry->GetVolumeID();
	event->directoryID = entry->GetDirectoryID();
	event->nodeVolumeID = entry->GetNode()->GetVolumeID();
	event->nodeID = entry->GetNode()->GetID();
	event->name.SetTo(entry->GetName());

	if (_event) {
		*_event = event;
	} else {
		_EntryRemoved(event, false);
		event->ReleaseReference();
	}

	return B_OK;
}

// _CheckVolumeRootMoved
void
VolumeManager::_CheckVolumeRootMoved(EntryMovedEvent* event)
{
	// If a volume root is moved, the sent node monitoring message does
	// unforunately contain the node_ref of the covered node, not that of the
	// volume root -- a BeOS R5 VFS bug. Since we have the entry_ref of the
	// new entry, we can stat the node.

	// check whether the node is the root of a volume
	NoAllocEntryRef ref(event->volumeID, event->toDirectoryID,
		event->toName.GetString());
	BEntry entry;
	struct stat st;
	if (FDManager::SetEntry(&entry, &ref) == B_OK
		&& entry.GetStat(&st) == B_OK) {
		event->nodeVolumeID = st.st_dev;
		event->nodeID = st.st_ino;
		if (Volume* volume = GetVolume(st.st_dev)) {
			if (volume->GetRootID() == st.st_ino) {
				PRINT("Mount point for volume %" B_PRIdDEV " renamed\n",
					volume->GetID());
			}
		}
	}
}

// _NodeMonitoringProcessorEntry
int32
VolumeManager::_NodeMonitoringProcessorEntry(void* data)
{
	return ((VolumeManager*)data)->_NodeMonitoringProcessor();
}

// _NodeMonitoryProcessor
int32
VolumeManager::_NodeMonitoringProcessor()
{
	do {
		NodeMonitoringEvent* event = NULL;
		status_t error = fNodeMonitoringEvents.Pop(&event);

		VolumeManagerLocker managerLocker;

		while (error == B_OK) {
			if (event->queryHandler) {
				switch (event->opcode) {
					case B_ENTRY_CREATED:
						_QueryEntryCreated(
							dynamic_cast<EntryCreatedEvent*>(event));
						break;
					case B_ENTRY_REMOVED:
						_QueryEntryRemoved(
							dynamic_cast<EntryRemovedEvent*>(event));
						break;
					case B_ENTRY_MOVED:
						_QueryEntryMoved(dynamic_cast<EntryMovedEvent*>(event));
						break;
				}
			} else {
				switch (event->opcode) {
					case B_ENTRY_CREATED:
						_EntryCreated(dynamic_cast<EntryCreatedEvent*>(event));
						break;
					case B_ENTRY_REMOVED:
						_EntryRemoved(dynamic_cast<EntryRemovedEvent*>(event),
							false);
						break;
					case B_ENTRY_MOVED:
						_EntryMoved(dynamic_cast<EntryMovedEvent*>(event));
						break;
					case B_STAT_CHANGED:
						_NodeStatChanged(
							dynamic_cast<StatChangedEvent*>(event));
						break;
					case B_ATTR_CHANGED:
						_NodeAttributeChanged(
							dynamic_cast<AttributeChangedEvent*>(event));
						break;
					case B_DEVICE_MOUNTED:
						_VolumeMounted(dynamic_cast<VolumeMountedEvent*>(event));
						break;
					case B_DEVICE_UNMOUNTED:
						_VolumeUnmounted(
							dynamic_cast<VolumeUnmountedEvent*>(event));
						break;
				}
			}
			event->ReleaseReference();

			// If there is another event available, get it as long as we
			// have the VolumeManager lock.
			error = fNodeMonitoringEvents.Pop(&event, 0);
		}
	} while (!fTerminating);

	return 0;
}


// sManager
VolumeManager* VolumeManager::sManager = NULL;