/*
 * Copyright 2007-2013, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Axel Dörfler, axeld@pinc-software.de
 *		Stephan Aßmus, superstippi@gmx.de
 *		Ingo Weinhold, ingo_weinhold@gmx.de
 */
 
 
#include <PathMonitor.h>
 
#include <pthread.h>
#include <stdio.h>
 
#include <Autolock.h>
#include <Directory.h>
#include <Entry.h>
#include <Handler.h>
#include <Locker.h>
#include <Looper.h>
#include <Path.h>
#include <String.h>
 
#include <AutoDeleter.h>
#include <NotOwningEntryRef.h>
#include <ObjectList.h>
#include <util/OpenHashTable.h>
#include <util/SinglyLinkedList.h>
 
 
#undef TRACE
//#define TRACE_PATH_MONITOR
#ifdef TRACE_PATH_MONITOR
#	define TRACE(...) debug_printf("BPathMonitor: " __VA_ARGS__)
#else
#	define TRACE(...) ;
#endif
 
 
// TODO: Support symlink components in the path.
// TODO: Support mounting/unmounting of volumes in path components and within
// the watched path tree.
 
 
#define WATCH_NODE_FLAG_MASK	0x00ff
 
 
namespace {
 
 
class Directory;
class Node;
struct WatcherHashDefinition;
typedef BOpenHashTable<WatcherHashDefinition> WatcherMap;
 
 
static pthread_once_t sInitOnce = PTHREAD_ONCE_INIT;
static WatcherMap* sWatchers = NULL;
static BLooper* sLooper = NULL;
static BPathMonitor::BWatchingInterface* sDefaultWatchingInterface = NULL;
static BPathMonitor::BWatchingInterface* sWatchingInterface = NULL;
 
 
//	#pragma mark -
 
 
/*! Returns empty path, if either \a parent or \a subPath is empty or an
	allocation fails.
 */
static BString
make_path(const BString& parent, const char* subPath)
{
	BString path = parent;
	int32 length = path.Length();
	if (length == 0 || subPath[0] == '\0')
		return BString();
 
	if (parent.ByteAt(length - 1) != '/') {
		path << '/';
		if (path.Length() < ++length)
			return BString();
	}
 
	path << subPath;
	if (path.Length() <= length)
		return BString();
	return path;
}
 
 
//	#pragma mark - Ancestor
 
 
class Ancestor {
public:
	Ancestor(Ancestor* parent, const BString& path, size_t pathComponentOffset)
		:
		fParent(parent),
		fChild(NULL),
		fPath(path),
		fEntryRef(-1, -1, fPath.String() + pathComponentOffset),
		fNodeRef(),
		fWatchingFlags(0),
		fIsDirectory(false)
	{
		if (pathComponentOffset == 0) {
			// must be "/"
			fEntryRef.SetTo(-1, -1, ".");
		}
 
		if (fParent != NULL)
			fParent->fChild = this;
	}
 
	Ancestor* Parent() const
	{
		return fParent;
	}
 
	Ancestor* Child() const
	{
		return fChild;
	}
 
	const BString& Path() const
	{
		return fPath;
	}
 
	const char* Name() const
	{
		return fEntryRef.name;
	}
 
	bool Exists() const
	{
		return fNodeRef.device >= 0;
	}
 
	const NotOwningEntryRef& EntryRef() const
	{
		return fEntryRef;
	}
 
	const node_ref& NodeRef() const
	{
		return fNodeRef;
	}
 
	bool IsDirectory() const
	{
		return fIsDirectory;
	}
 
	status_t StartWatching(uint32 pathFlags, BHandler* target)
	{
		// init entry ref
		BEntry entry;
		status_t error = entry.SetTo(fPath);
		if (error != B_OK)
			return error;
 
		entry_ref entryRef;
		error = entry.GetRef(&entryRef);
		if (error != B_OK)
			return error;
 
		fEntryRef.device = entryRef.device;
		fEntryRef.directory = entryRef.directory;
 
		// init node ref
		struct stat st;
		error = entry.GetStat(&st);
		if (error != B_OK)
			return error == B_ENTRY_NOT_FOUND ? B_OK : error;
 
		fNodeRef = node_ref(st.st_dev, st.st_ino);
		fIsDirectory = S_ISDIR(st.st_mode);
 
		// start watching
		uint32 flags = fChild == NULL ? pathFlags : B_WATCH_DIRECTORY;
			// In theory B_WATCH_NAME would suffice for all existing ancestors,
			// plus B_WATCH_DIRECTORY for the parent of the first not existing
			// ancestor. In practice this complicates the transitions when an
			// ancestor is created/removed/moved.
		if (flags != 0) {
			error = sWatchingInterface->WatchNode(&fNodeRef, flags, target);
			TRACE("  started to watch ancestor %p (\"%s\", %#" B_PRIx32
				") -> %s\n", this, Name(), flags, strerror(error));
			if (error != B_OK)
				return error;
		}
 
		fWatchingFlags = flags;
		return B_OK;
	}
 
	void StopWatching(BHandler* target)
	{
		// stop watching
		if (fWatchingFlags != 0) {
			sWatchingInterface->WatchNode(&fNodeRef, B_STOP_WATCHING, target);
			fWatchingFlags = 0;
		}
 
		// uninitialize node and entry ref
		fIsDirectory = false;
		fNodeRef = node_ref();
		fEntryRef.SetDirectoryNodeRef(node_ref());
	}
 
	Ancestor*& HashNext()
	{
		return fHashNext;
	}
 
private:
	Ancestor*			fParent;
	Ancestor*			fChild;
	Ancestor*			fHashNext;
	BString				fPath;
	NotOwningEntryRef	fEntryRef;
	node_ref			fNodeRef;
	uint32				fWatchingFlags;
	bool				fIsDirectory;
};
 
 
//	#pragma mark - AncestorMap
 
 
struct AncestorHashDefinition {
	typedef	node_ref	KeyType;
	typedef	Ancestor	ValueType;
 
	size_t HashKey(const node_ref& key) const
	{
		return size_t(key.device ^ key.node);
	}
 
	size_t Hash(Ancestor* value) const
	{
		return HashKey(value->NodeRef());
	}
 
	bool Compare(const node_ref& key, Ancestor* value) const
	{
		return key == value->NodeRef();
	}
 
	Ancestor*& GetLink(Ancestor* value) const
	{
		return value->HashNext();
	}
};
 
 
typedef BOpenHashTable<AncestorHashDefinition> AncestorMap;
 
 
//	#pragma mark - Entry
 
 
class Entry : public SinglyLinkedListLinkImpl<Entry> {
public:
	Entry(Directory* parent, const BString& name, ::Node* node)
		:
		fParent(parent),
		fName(name),
		fNode(node)
	{
	}
 
	Directory* Parent() const
	{
		return fParent;
	}
 
	const BString& Name() const
	{
		return fName;
	}
 
	::Node* Node() const
	{
		return fNode;
	}
 
	void SetNode(::Node* node)
	{
		fNode = node;
	}
 
	inline NotOwningEntryRef EntryRef() const;
 
	Entry*& HashNext()
	{
		return fHashNext;
	}
 
private:
	Directory*	fParent;
	BString		fName;
	::Node*		fNode;
	Entry*		fHashNext;
};
 
typedef SinglyLinkedList<Entry> EntryList;
 
 
// EntryMap
 
 
struct EntryHashDefinition {
	typedef	const char*	KeyType;
	typedef	Entry		ValueType;
 
	size_t HashKey(const char* key) const
	{
		return BString::HashValue(key);
	}
 
	size_t Hash(Entry* value) const
	{
		return value->Name().HashValue();
	}
 
	bool Compare(const char* key, Entry* value) const
	{
		return value->Name() == key;
	}
 
	Entry*& GetLink(Entry* value) const
	{
		return value->HashNext();
	}
};
 
 
typedef BOpenHashTable<EntryHashDefinition> EntryMap;
 
 
//	#pragma mark - Node
 
 
class Node {
public:
	Node(const node_ref& nodeRef)
		:
		fNodeRef(nodeRef)
	{
	}
 
	virtual ~Node()
	{
	}
 
	virtual bool IsDirectory() const
	{
		return false;
	}
 
	virtual Directory* ToDirectory()
	{
		return NULL;
	}
 
	const node_ref& NodeRef() const
	{
		return fNodeRef;
	}
 
	const EntryList& Entries() const
	{
		return fEntries;
	}
 
	bool HasEntries() const
	{
		return !fEntries.IsEmpty();
	}
 
	Entry* FirstNodeEntry() const
	{
		return fEntries.Head();
	}
 
	bool IsOnlyNodeEntry(Entry* entry) const
	{
		return entry == fEntries.Head() && fEntries.GetNext(entry) == NULL;
	}
 
	void AddNodeEntry(Entry* entry)
	{
		fEntries.Add(entry);
	}
 
	void RemoveNodeEntry(Entry* entry)
	{
		fEntries.Remove(entry);
	}
 
	Node*& HashNext()
	{
		return fHashNext;
	}
 
private:
	node_ref			fNodeRef;
	EntryList			fEntries;
	Node*				fHashNext;
};
 
 
struct NodeHashDefinition {
	typedef	node_ref	KeyType;
	typedef	Node		ValueType;
 
	size_t HashKey(const node_ref& key) const
	{
		return size_t(key.device ^ key.node);
	}
 
	size_t Hash(Node* value) const
	{
		return HashKey(value->NodeRef());
	}
 
	bool Compare(const node_ref& key, Node* value) const
	{
		return key == value->NodeRef();
	}
 
	Node*& GetLink(Node* value) const
	{
		return value->HashNext();
	}
};
 
 
typedef BOpenHashTable<NodeHashDefinition> NodeMap;
 
 
//	#pragma mark - Directory
 
 
class Directory : public Node {
public:
	static Directory* Create(const node_ref& nodeRef)
	{
		Directory* directory = new(std::nothrow) Directory(nodeRef);
		if (directory == NULL || directory->fEntries.Init() != B_OK) {
			delete directory;
			return NULL;
		}
 
		return directory;
	}
 
	virtual bool IsDirectory() const
	{
		return true;
	}
 
	virtual Directory* ToDirectory()
	{
		return this;
	}
 
	Entry* FindEntry(const char* name) const
	{
		return fEntries.Lookup(name);
	}
 
	Entry* CreateEntry(const BString& name, Node* node)
	{
		Entry* entry = new(std::nothrow) Entry(this, name, node);
		if (entry == NULL || entry->Name().IsEmpty()) {
			delete entry;
			return NULL;
		}
 
		AddEntry(entry);
		return entry;
	}
 
	void AddEntry(Entry* entry)
	{
		fEntries.Insert(entry);
	}
 
	void RemoveEntry(Entry* entry)
	{
		fEntries.Remove(entry);
	}
 
	EntryMap::Iterator GetEntryIterator() const
	{
		return fEntries.GetIterator();
	}
 
	Entry* RemoveAllEntries()
	{
		return fEntries.Clear(true);
	}
 
private:
	Directory(const node_ref& nodeRef)
		:
		Node(nodeRef)
	{
	}
 
private:
	EntryMap	fEntries;
};
 
 
//	#pragma mark - Entry
 
 
inline NotOwningEntryRef
Entry::EntryRef() const
{
	return NotOwningEntryRef(fParent->NodeRef(), fName);
}
 
 
//	#pragma mark - PathHandler
 
 
class PathHandler : public BHandler {
public:
								PathHandler(const char* path, uint32 flags,
									const BMessenger& target, BLooper* looper);
	virtual						~PathHandler();
 
			status_t			InitCheck() const;
			void				Quit();
 
			const BString&		OriginalPath() const
									{ return fOriginalPath; }
			uint32				Flags() const	{ return fFlags; }
 
	virtual	void				MessageReceived(BMessage* message);
 
			PathHandler*&		HashNext()	{ return fHashNext; }
 
private:
			status_t			_CreateAncestors();
			status_t			_StartWatchingAncestors(Ancestor* ancestor,
									bool notify);
			void				_StopWatchingAncestors(Ancestor* ancestor,
									bool notify);
 
			void				_EntryCreated(BMessage* message);
			void				_EntryRemoved(BMessage* message);
			void				_EntryMoved(BMessage* message);
			void				_NodeChanged(BMessage* message);
 
			bool				_EntryCreated(const NotOwningEntryRef& entryRef,
									const node_ref& nodeRef, bool isDirectory,
									bool dryRun, bool notify, Entry** _entry);
			bool				_EntryRemoved(const NotOwningEntryRef& entryRef,
									const node_ref& nodeRef, bool dryRun,
									bool notify, Entry** _keepEntry);
 
			bool				_CheckDuplicateEntryNotification(int32 opcode,
									const entry_ref& toEntryRef,
									const node_ref& nodeRef,
									const entry_ref* fromEntryRef = NULL);
			void				_UnsetDuplicateEntryNotification();
 
			Ancestor*			_GetAncestor(const node_ref& nodeRef) const;
 
			status_t			_AddNode(const node_ref& nodeRef,
									bool isDirectory, bool notify,
									Entry* entry = NULL, Node** _node = NULL);
			void				_DeleteNode(Node* node, bool notify);
			Node*				_GetNode(const node_ref& nodeRef) const;
 
			status_t			_AddEntryIfNeeded(Directory* directory,
									const char* name, const node_ref& nodeRef,
									bool isDirectory, bool notify,
									Entry** _entry = NULL);
			void				_DeleteEntry(Entry* entry, bool notify);
			void				_DeleteEntryAlreadyRemovedFromParent(
									Entry* entry, bool notify);
 
			void				_NotifyFilesCreatedOrRemoved(Entry* entry,
									int32 opcode) const;
			void				_NotifyEntryCreatedOrRemoved(Entry* entry,
									int32 opcode) const;
			void				_NotifyEntryCreatedOrRemoved(
									const entry_ref& entryRef,
									const node_ref& nodeRef, const char* path,
									bool isDirectory, int32 opcode) const;
			void				_NotifyEntryMoved(const entry_ref& fromEntryRef,
									const entry_ref& toEntryRef,
									const node_ref& nodeRef,
									const char* fromPath, const char* path,
									bool isDirectory, bool wasAdded,
									bool wasRemoved) const;
			void				_NotifyTarget(BMessage& message,
									const char* path) const;
 
			BString				_NodePath(const Node* node) const;
			BString				_EntryPath(const Entry* entry) const;
 
 
			bool				_WatchRecursively() const;
			bool				_WatchFilesOnly() const;
			bool				_WatchDirectoriesOnly() const;
 
private:
			BMessenger			fTarget;
			uint32				fFlags;
			status_t			fStatus;
			BString				fOriginalPath;
			BString				fPath;
			Ancestor*			fRoot;
			Ancestor*			fBaseAncestor;
			Node*				fBaseNode;
			AncestorMap			fAncestors;
			NodeMap				fNodes;
			PathHandler*		fHashNext;
			int32				fDuplicateEntryNotificationOpcode;
			node_ref			fDuplicateEntryNotificationNodeRef;
			entry_ref			fDuplicateEntryNotificationToEntryRef;
			entry_ref			fDuplicateEntryNotificationFromEntryRef;
};
 
 
struct PathHandlerHashDefinition {
	typedef	const char*	KeyType;
	typedef	PathHandler	ValueType;
 
	size_t HashKey(const char* key) const
	{
		return BString::HashValue(key);
	}
 
	size_t Hash(PathHandler* value) const
	{
		return value->OriginalPath().HashValue();
	}
 
	bool Compare(const char* key, PathHandler* value) const
	{
		return key == value->OriginalPath();
	}
 
	PathHandler*& GetLink(PathHandler* value) const
	{
		return value->HashNext();
	}
};
 
 
typedef BOpenHashTable<PathHandlerHashDefinition> PathHandlerMap;
 
 
//	#pragma mark - Watcher
 
 
struct Watcher : public PathHandlerMap {
	static Watcher* Create(const BMessenger& target)
	{
		Watcher* watcher = new(std::nothrow) Watcher(target);
		if (watcher == NULL || watcher->Init() != B_OK) {
			delete watcher;
			return NULL;
		}
		return watcher;
	}
 
	const BMessenger& Target() const
	{
		return fTarget;
	}
 
	Watcher*& HashNext()
	{
		return fHashNext;
	}
 
private:
	Watcher(const BMessenger& target)
		:
		fTarget(target)
	{
	}
 
private:
	BMessenger		fTarget;
	Watcher*		fHashNext;
};
 
 
struct WatcherHashDefinition {
	typedef	BMessenger	KeyType;
	typedef	Watcher		ValueType;
 
	size_t HashKey(const BMessenger& key) const
	{
		return key.HashValue();
	}
 
	size_t Hash(Watcher* value) const
	{
		return HashKey(value->Target());
	}
 
	bool Compare(const BMessenger& key, Watcher* value) const
	{
		return key == value->Target();
	}
 
	Watcher*& GetLink(Watcher* value) const
	{
		return value->HashNext();
	}
};
 
 
//	#pragma mark - PathHandler
 
 
PathHandler::PathHandler(const char* path, uint32 flags,
	const BMessenger& target, BLooper* looper)
	:
	BHandler(path),
	fTarget(target),
	fFlags(flags),
	fStatus(B_OK),
	fOriginalPath(path),
	fPath(),
	fRoot(NULL),
	fBaseAncestor(NULL),
	fBaseNode(NULL),
	fAncestors(),
	fNodes()
{
	TRACE("%p->PathHandler::PathHandler(\"%s\", %#" B_PRIx32 ")\n", this, path,
		flags);
 
	_UnsetDuplicateEntryNotification();
 
	fStatus = fAncestors.Init();
	if (fStatus != B_OK)
		return;
 
	fStatus = fNodes.Init();
	if (fStatus != B_OK)
		return;
 
	// normalize the flags
	if ((fFlags & B_WATCH_RECURSIVELY) != 0) {
		// We add B_WATCH_NAME and B_WATCH_DIRECTORY as needed, so clear them
		// here.
		fFlags &= ~uint32(B_WATCH_NAME | B_WATCH_DIRECTORY);
	} else {
		// The B_WATCH_*_ONLY flags are only valid for the recursive mode.
		// B_WATCH_NAME is implied (we watch the parent directory).
		fFlags &= ~uint32(B_WATCH_FILES_ONLY | B_WATCH_DIRECTORIES_ONLY
			| B_WATCH_NAME);
	}
 
	// Normalize the path a bit. We can't use BPath, as it may really normalize
	// the path, i.e. resolve symlinks and such, which may cause us to monitor
	// the wrong path. We want some normalization, though:
	// * relative -> absolute path
	// * fold duplicate '/'s
	// * omit "." components
	// * fail when encountering ".." components
 
	// make absolute
	BString normalizedPath;
	if (path[0] == '/') {
		normalizedPath = "/";
		path++;
	} else
		normalizedPath = BPath(".").Path();
	if (normalizedPath.IsEmpty()) {
		fStatus = B_NO_MEMORY;
		return;
	}
 
	// parse path components
	const char* pathEnd = path + strlen(path);
	for (;;) {
		// skip '/'s
		while (path[0] == '/')
			path++;
		if (path == pathEnd)
			break;
 
		const char* componentEnd = strchr(path, '/');
		if (componentEnd == NULL)
			componentEnd = pathEnd;
		size_t componentLength = componentEnd - path;
 
		// handle ".' and ".."
		if (path[0] == '.') {
			if (componentLength == 1) {
				path = componentEnd;
				continue;
			}
			if (componentLength == 2 && path[1] == '.') {
				fStatus = B_BAD_VALUE;
				return;
			}
		}
 
		int32 normalizedPathLength = normalizedPath.Length();
		if (normalizedPath.ByteAt(normalizedPathLength - 1) != '/') {
			normalizedPath << '/';
			normalizedPathLength++;
		}
		normalizedPath.Append(path, componentEnd - path);
		normalizedPathLength += int32(componentEnd - path);
 
		if (normalizedPath.Length() != normalizedPathLength) {
			fStatus = B_NO_MEMORY;
			return;
		}
 
		path = componentEnd;
	}
 
	fPath = normalizedPath;
 
	// Create the Ancestor objects -- they correspond to the path components and
	// are used for watching changes that affect the entries on the path.
	fStatus = _CreateAncestors();
	if (fStatus != B_OK)
		return;
 
	// add ourselves to the looper
	looper->AddHandler(this);
 
	// start watching
	fStatus = _StartWatchingAncestors(fRoot, false);
	if (fStatus != B_OK)
		return;
}
 
 
PathHandler::~PathHandler()
{
	TRACE("%p->PathHandler::~PathHandler(\"%s\", %#" B_PRIx32 ")\n", this,
		fPath.String(), fFlags);
 
	if (fBaseNode != NULL)
		_DeleteNode(fBaseNode, false);
 
	while (fRoot != NULL) {
		Ancestor* nextAncestor = fRoot->Child();
		delete fRoot;
		fRoot = nextAncestor;
	}
}
 
 
status_t
PathHandler::InitCheck() const
{
	return fStatus;
}
 
 
void
PathHandler::Quit()
{
	TRACE("%p->PathHandler::Quit()\n", this);
	sWatchingInterface->StopWatching(this);
	sLooper->RemoveHandler(this);
	delete this;
}
 
 
void
PathHandler::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case B_NODE_MONITOR:
		{
			int32 opcode;
			if (message->FindInt32("opcode", &opcode) != B_OK)
				return;
 
			switch (opcode) {
				case B_ENTRY_CREATED:
					_EntryCreated(message);
					break;
 
				case B_ENTRY_REMOVED:
					_EntryRemoved(message);
					break;
 
				case B_ENTRY_MOVED:
					_EntryMoved(message);
					break;
 
				default:
					_UnsetDuplicateEntryNotification();
					_NodeChanged(message);
					break;
			}
 
			break;
		}
 
		default:
			BHandler::MessageReceived(message);
			break;
	}
}
 
 
status_t
PathHandler::_CreateAncestors()
{
	TRACE("%p->PathHandler::_CreateAncestors()\n", this);
 
	// create the Ancestor objects
	const char* path = fPath.String();
	const char* pathEnd = path + fPath.Length();
	const char* component = path;
 
	Ancestor* ancestor = NULL;
 
	while (component < pathEnd) {
		const char* componentEnd = component == path
			? component + 1 : strchr(component, '/');
		if (componentEnd == NULL)
			componentEnd = pathEnd;
 
		BString ancestorPath(path, componentEnd - path);
		if (ancestorPath.IsEmpty())
			return B_NO_MEMORY;
 
		ancestor = new(std::nothrow) Ancestor(ancestor, ancestorPath,
			component - path);
		TRACE("  created ancestor %p (\"%s\" / \"%s\")\n", ancestor,
			ancestor->Path().String(), ancestor->Name());
		if (ancestor == NULL)
			return B_NO_MEMORY;
 
		if (fRoot == NULL)
			fRoot = ancestor;
 
		component = componentEnd[0] == '/' ? componentEnd + 1 : componentEnd;
	}
 
	fBaseAncestor = ancestor;
 
	return B_OK;
}
 
 
status_t
PathHandler::_StartWatchingAncestors(Ancestor* startAncestor, bool notify)
{
	TRACE("%p->PathHandler::_StartWatchingAncestors(%p, %d)\n", this,
		startAncestor, notify);
 
	// The watch flags for the path (if it exists). Recursively implies
	// directory, since we need to watch the entries.
	uint32 watchFlags = (fFlags & WATCH_NODE_FLAG_MASK)
		| (_WatchRecursively() ? B_WATCH_DIRECTORY : 0);
 
	for (Ancestor* ancestor = startAncestor; ancestor != NULL;
		ancestor = ancestor->Child()) {
		status_t error = ancestor->StartWatching(watchFlags, this);
		if (error != B_OK)
			return error;
 
		if (!ancestor->Exists()) {
			TRACE("  -> ancestor doesn't exist\n");
			break;
		}
 
		fAncestors.Insert(ancestor);
	}
 
	if (!fBaseAncestor->Exists())
		return B_OK;
 
	if (notify) {
		_NotifyEntryCreatedOrRemoved(fBaseAncestor->EntryRef(),
			fBaseAncestor->NodeRef(), fPath, fBaseAncestor->IsDirectory(),
			B_ENTRY_CREATED);
	}
 
	if (!_WatchRecursively())
		return B_OK;
 
	status_t error = _AddNode(fBaseAncestor->NodeRef(),
		fBaseAncestor->IsDirectory(), notify && _WatchFilesOnly(), NULL,
		&fBaseNode);
	if (error != B_OK)
		return error;
 
	return B_OK;
}
 
 
void
PathHandler::_StopWatchingAncestors(Ancestor* ancestor, bool notify)
{
	// stop watching the tree below path
	if (fBaseNode != NULL) {
		_DeleteNode(fBaseNode, notify && _WatchFilesOnly());
		fBaseNode = NULL;
	}
 
	if (notify && fBaseAncestor->Exists()
		&& (fBaseAncestor->IsDirectory()
			? !_WatchFilesOnly() : !_WatchDirectoriesOnly())) {
		_NotifyEntryCreatedOrRemoved(fBaseAncestor->EntryRef(),
			fBaseAncestor->NodeRef(), fPath, fBaseAncestor->IsDirectory(),
			B_ENTRY_REMOVED);
	}
 
	// stop watching the ancestors and uninitialize their entries
	for (; ancestor != NULL; ancestor = ancestor->Child()) {
		if (ancestor->Exists())
			fAncestors.Remove(ancestor);
		ancestor->StopWatching(this);
	}
}
 
 
void
PathHandler::_EntryCreated(BMessage* message)
{
	// TODO: Unless we're watching files only, we might want to forward (some
	// of) the messages that don't agree with our model, since our client
	// maintains its model at a different time and the notification might be
	// necessary to keep it up-to-date. E.g. consider the following case:
	// 1. a directory is created
	// 2. a file is created in the directory
	// 3. the file is removed from the directory
	// If we get the notification after 1. and before 2., we pass it on to the
	// client, which may get it after 2. and before 3., thus seeing the file.
	// If we then get the entry-created notification after 3., we don't see the
	// file anymore and ignore the notification as well as the following
	// entry-removed notification. That is the client will never know that the
	// file has been removed. This can only happen in recursive mode. Otherwise
	// (and with B_WATCH_DIRECTORY) we just pass on all notifications.
	// A possible solution could be to just create a zombie entry and pass on
	// the entry-created notification. We wouldn't be able to adhere to the
	// B_WATCH_FILES_ONLY/B_WATCH_DIRECTORIES_ONLY flags, but that should be
	// acceptable. Either the client hasn't seen the entry either -- then it
	// doesn't matter -- or it likely has ignored a not matching entry anyway.
 
	NotOwningEntryRef entryRef;
	node_ref nodeRef;
 
	if (message->FindInt32("device", &nodeRef.device) != B_OK
		|| message->FindInt64("node", &nodeRef.node) != B_OK
		|| message->FindInt64("directory", &entryRef.directory) != B_OK
		|| message->FindString("name", (const char**)&entryRef.name) != B_OK) {
		return;
	}
	entryRef.device = nodeRef.device;
 
	if (_CheckDuplicateEntryNotification(B_ENTRY_CREATED, entryRef, nodeRef))
		return;
 
	TRACE("%p->PathHandler::_EntryCreated(): entry: %" B_PRIdDEV ":%" B_PRIdINO
		":\"%s\", node: %" B_PRIdDEV ":%" B_PRIdINO "\n", this, entryRef.device,
		entryRef.directory, entryRef.name, nodeRef.device, nodeRef.node);
 
	BEntry entry;
	struct stat st;
	if (entry.SetTo(&entryRef) != B_OK || entry.GetStat(&st) != B_OK
		|| nodeRef != node_ref(st.st_dev, st.st_ino)) {
		return;
	}
 
	_EntryCreated(entryRef, nodeRef, S_ISDIR(st.st_mode), false, true, NULL);
}
 
 
void
PathHandler::_EntryRemoved(BMessage* message)
{
	NotOwningEntryRef entryRef;
	node_ref nodeRef;
 
	if (message->FindInt32("device", &nodeRef.device) != B_OK
		|| message->FindInt64("node", &nodeRef.node) != B_OK
		|| message->FindInt64("directory", &entryRef.directory) != B_OK
		|| message->FindString("name", (const char**)&entryRef.name) != B_OK) {
		return;
	}
	entryRef.device = nodeRef.device;
 
	if (_CheckDuplicateEntryNotification(B_ENTRY_REMOVED, entryRef, nodeRef))
		return;
 
	TRACE("%p->PathHandler::_EntryRemoved(): entry: %" B_PRIdDEV ":%" B_PRIdINO
		":\"%s\", node: %" B_PRIdDEV ":%" B_PRIdINO "\n", this, entryRef.device,
		entryRef.directory, entryRef.name, nodeRef.device, nodeRef.node);
 
	_EntryRemoved(entryRef, nodeRef, false, true, NULL);
}
 
 
void
PathHandler::_EntryMoved(BMessage* message)
{
	NotOwningEntryRef fromEntryRef;
	NotOwningEntryRef toEntryRef;
	node_ref nodeRef;
 
	if (message->FindInt32("node device", &nodeRef.device) != B_OK
		|| message->FindInt64("node", &nodeRef.node) != B_OK
		|| message->FindInt32("device", &fromEntryRef.device) != B_OK
		|| message->FindInt64("from directory", &fromEntryRef.directory) != B_OK
		|| message->FindInt64("to directory", &toEntryRef.directory) != B_OK
		|| message->FindString("from name", (const char**)&fromEntryRef.name)
			!= B_OK
		|| message->FindString("name", (const char**)&toEntryRef.name)
			!= B_OK) {
		return;
	}
	toEntryRef.device = fromEntryRef.device;
 
	if (_CheckDuplicateEntryNotification(B_ENTRY_MOVED, toEntryRef, nodeRef,
			&fromEntryRef)) {
		return;
	}
 
	TRACE("%p->PathHandler::_EntryMoved(): entry: %" B_PRIdDEV ":%" B_PRIdINO
		":\"%s\" -> %" B_PRIdDEV ":%" B_PRIdINO ":\"%s\", node: %" B_PRIdDEV
		":%" B_PRIdINO "\n", this, fromEntryRef.device, fromEntryRef.directory,
		fromEntryRef.name, toEntryRef.device, toEntryRef.directory,
		toEntryRef.name, nodeRef.device, nodeRef.node);
 
	BEntry entry;
	struct stat st;
	if (entry.SetTo(&toEntryRef) != B_OK || entry.GetStat(&st) != B_OK
		|| nodeRef != node_ref(st.st_dev, st.st_ino)) {
		_EntryRemoved(fromEntryRef, nodeRef, false, true, NULL);
		return;
	}
	bool isDirectory = S_ISDIR(st.st_mode);
 
	Ancestor* fromAncestor = _GetAncestor(fromEntryRef.DirectoryNodeRef());
	Ancestor* toAncestor = _GetAncestor(toEntryRef.DirectoryNodeRef());
 
	if (_WatchRecursively()) {
		Node* fromDirectoryNode = _GetNode(fromEntryRef.DirectoryNodeRef());
		Node* toDirectoryNode = _GetNode(toEntryRef.DirectoryNodeRef());
		if (fromDirectoryNode != NULL || toDirectoryNode != NULL) {
			// Check whether _EntryRemoved()/_EntryCreated() can handle the
			// respective entry regularly (i.e. don't encounter an out-of-sync
			// issue) or don't need to be called at all (entry outside the
			// monitored tree).
			if ((fromDirectoryNode == NULL
					|| _EntryRemoved(fromEntryRef, nodeRef, true, false, NULL))
				&& (toDirectoryNode == NULL
					|| _EntryCreated(toEntryRef, nodeRef, isDirectory, true,
						false, NULL))) {
				// The entries can be handled regularly. We delegate the work to
				// _EntryRemoved() and _EntryCreated() and only handle the
				// notification ourselves.
 
				// handle removed
				Entry* removedEntry = NULL;
				if (fromDirectoryNode != NULL) {
					_EntryRemoved(fromEntryRef, nodeRef, false, false,
						&removedEntry);
				}
 
				// handle created
				Entry* createdEntry = NULL;
				if (toDirectoryNode != NULL) {
					_EntryCreated(toEntryRef, nodeRef, isDirectory, false,
						false, &createdEntry);
				}
 
				// notify
				if (_WatchFilesOnly() && isDirectory) {
					// recursively iterate through the removed and created
					// hierarchy and send notifications for the files
					if (removedEntry != NULL) {
						_NotifyFilesCreatedOrRemoved(removedEntry,
							B_ENTRY_REMOVED);
					}
 
					if (createdEntry != NULL) {
						_NotifyFilesCreatedOrRemoved(createdEntry,
							B_ENTRY_CREATED);
					}
				} else {
					BString fromPath;
					if (fromDirectoryNode != NULL) {
						fromPath = make_path(_NodePath(fromDirectoryNode),
							fromEntryRef.name);
					}
 
					BString path;
					if (toDirectoryNode != NULL) {
						path = make_path(_NodePath(toDirectoryNode),
							toEntryRef.name);
					}
 
					_NotifyEntryMoved(fromEntryRef, toEntryRef, nodeRef,
						fromPath, path, isDirectory, fromDirectoryNode == NULL,
						toDirectoryNode == NULL);
				}
 
				if (removedEntry != NULL)
					_DeleteEntry(removedEntry, false);
			} else {
				// The entries can't be handled regularly. We delegate all the
				// work to _EntryRemoved() and _EntryCreated(). This will
				// generate separate entry-removed and entry-created
				// notifications.
 
				// handle removed
				if (fromDirectoryNode != NULL)
					_EntryRemoved(fromEntryRef, nodeRef, false, true, NULL);
 
				// handle created
				if (toDirectoryNode != NULL) {
					_EntryCreated(toEntryRef, nodeRef, isDirectory, false, true,
						NULL);
				}
			}
 
			return;
		}
 
		if (fromAncestor == fBaseAncestor || toAncestor == fBaseAncestor) {
			// That should never happen, as we should have found a matching
			// directory node in this case.
#ifdef DEBUG
			debugger("path ancestor exists, but doesn't have a directory");
			// Could actually be an out-of-memory situation, if we simply failed
			// to create the directory earlier.
#endif
			_StopWatchingAncestors(fRoot, false);
			_StartWatchingAncestors(fRoot, false);
			return;
		}
	} else {
		// Non-recursive mode: This notification is only of interest to us, if
		// it is either a move into/within/out of the path and B_WATCH_DIRECTORY
		// is set, or an ancestor might be affected.
		if (fromAncestor == NULL && toAncestor == NULL)
			return;
 
		if (fromAncestor == fBaseAncestor || toAncestor == fBaseAncestor) {
			if ((fFlags & B_WATCH_DIRECTORY) != 0) {
				BString fromPath;
				if (fromAncestor == fBaseAncestor)
					fromPath = make_path(fPath, fromEntryRef.name);
 
				BString path;
				if (toAncestor == fBaseAncestor)
					path = make_path(fPath, toEntryRef.name);
 
				_NotifyEntryMoved(fromEntryRef, toEntryRef, nodeRef,
					fromPath, path, isDirectory, fromAncestor == NULL,
					toAncestor == NULL);
			}
			return;
		}
	}
 
	if (fromAncestor == NULL && toAncestor == NULL)
		return;
 
	if (fromAncestor == NULL) {
		_EntryCreated(toEntryRef, nodeRef, isDirectory, false, true, NULL);
		return;
	}
 
	if (toAncestor == NULL) {
		_EntryRemoved(fromEntryRef, nodeRef, false, true, NULL);
		return;
	}
 
	// An entry was moved in a true ancestor directory or between true ancestor
	// directories. Unless the moved entry was or becomes our base ancestor, we
	// let _EntryRemoved() and _EntryCreated() handle it.
	bool fromIsBase = fromAncestor == fBaseAncestor->Parent()
		&& strcmp(fromEntryRef.name, fBaseAncestor->Name()) == 0;
	bool toIsBase = toAncestor == fBaseAncestor->Parent()
		&& strcmp(toEntryRef.name, fBaseAncestor->Name()) == 0;
	if (fromIsBase || toIsBase) {
		// This might be a duplicate notification. Check whether our model
		// already reflects the change. Otherwise stop/start watching the base
		// ancestor as required.
		bool notifyFilesRecursively = _WatchFilesOnly() && isDirectory;
		if (fromIsBase) {
			if (!fBaseAncestor->Exists())
				return;
			_StopWatchingAncestors(fBaseAncestor, notifyFilesRecursively);
		} else {
			if (fBaseAncestor->Exists()) {
				if (fBaseAncestor->NodeRef() == nodeRef
					&& isDirectory == fBaseAncestor->IsDirectory()) {
					return;
				}
 
				// We're out of sync with reality.
				_StopWatchingAncestors(fBaseAncestor, true);
				_StartWatchingAncestors(fBaseAncestor, true);
				return;
			}
 
			_StartWatchingAncestors(fBaseAncestor, notifyFilesRecursively);
		}
 
		if (!notifyFilesRecursively) {
			_NotifyEntryMoved(fromEntryRef, toEntryRef, nodeRef,
				fromIsBase ? fPath.String() : NULL,
				toIsBase ? fPath.String() : NULL,
				isDirectory, toIsBase, fromIsBase);
		}
		return;
	}
 
	_EntryRemoved(fromEntryRef, nodeRef, false, true, NULL);
	_EntryCreated(toEntryRef, nodeRef, isDirectory, false, true, NULL);
}
 
 
void
PathHandler::_NodeChanged(BMessage* message)
{
	node_ref nodeRef;
 
	if (message->FindInt32("device", &nodeRef.device) != B_OK
		|| message->FindInt64("node", &nodeRef.node) != B_OK) {
		return;
	}
 
	TRACE("%p->PathHandler::_NodeChanged(): node: %" B_PRIdDEV ":%" B_PRIdINO
		", %s%s\n", this, nodeRef.device, nodeRef.node,
			message->GetInt32("opcode", B_STAT_CHANGED) == B_ATTR_CHANGED
				? "attribute: " : "stat",
			message->GetInt32("opcode", B_STAT_CHANGED) == B_ATTR_CHANGED
				? message->GetString("attr", "") : "");
 
	bool isDirectory = false;
	BString path;
	if (Ancestor* ancestor = _GetAncestor(nodeRef)) {
		if (ancestor != fBaseAncestor)
			return;
		isDirectory = ancestor->IsDirectory();
		path = fPath;
	} else if (Node* node = _GetNode(nodeRef)) {
		isDirectory = node->IsDirectory();
		path = _NodePath(node);
	} else
		return;
 
	if (isDirectory ? _WatchFilesOnly() : _WatchDirectoriesOnly())
		return;
 
	_NotifyTarget(*message, path);
}
 
 
bool
PathHandler::_EntryCreated(const NotOwningEntryRef& entryRef,
	const node_ref& nodeRef, bool isDirectory, bool dryRun, bool notify,
	Entry** _entry)
{
	if (_entry != NULL)
		*_entry = NULL;
 
	Ancestor* ancestor = _GetAncestor(nodeRef);
	if (ancestor != NULL) {
		if (isDirectory == ancestor->IsDirectory()
			&& entryRef == ancestor->EntryRef()) {
			// just a duplicate notification
			TRACE("  -> we already know the ancestor\n");
			return true;
		}
 
		struct stat ancestorStat;
		if (BEntry(&ancestor->EntryRef()).GetStat(&ancestorStat) == B_OK
			&& node_ref(ancestorStat.st_dev, ancestorStat.st_ino)
				== ancestor->NodeRef()
			&& S_ISDIR(ancestorStat.st_mode) == ancestor->IsDirectory()) {
			// Our information for the ancestor is up-to-date, so ignore the
			// notification.
			TRACE("  -> we know a different ancestor, but our info is "
				"up-to-date\n");
			return true;
		}
 
		// We're out of sync with reality.
		TRACE("  -> ancestor mismatch -> resyncing\n");
		if (!dryRun) {
			_StopWatchingAncestors(ancestor, true);
			_StartWatchingAncestors(ancestor, true);
		}
		return false;
	}
 
	ancestor = _GetAncestor(entryRef.DirectoryNodeRef());
	if (ancestor != NULL) {
		if (ancestor != fBaseAncestor) {
			// The directory is a true ancestor -- the notification is only of
			// interest, if the entry matches the child ancestor.
			Ancestor* childAncestor = ancestor->Child();
			if (strcmp(entryRef.name, childAncestor->Name()) != 0) {
				TRACE("  -> not an ancestor entry we're interested in "
					"(\"%s\")\n", childAncestor->Name());
				return true;
			}
 
			if (!dryRun) {
				if (childAncestor->Exists()) {
					TRACE("  ancestor entry mismatch -> resyncing\n");
					// We're out of sync with reality -- the new entry refers to
					// a different node.
					_StopWatchingAncestors(childAncestor, true);
				}
 
				TRACE("  -> starting to watch newly appeared ancestor\n");
				_StartWatchingAncestors(childAncestor, true);
			}
			return false;
		}
 
		// The directory is our path. If watching recursively, just fall
		// through. Otherwise, we want to pass on the notification, if directory
		// watching is enabled.
		if (!_WatchRecursively()) {
			if ((fFlags & B_WATCH_DIRECTORY) != 0) {
				_NotifyEntryCreatedOrRemoved(entryRef, nodeRef,
					make_path(fPath, entryRef.name), isDirectory,
					B_ENTRY_CREATED);
			}
			return true;
		}
	}
 
	if (!_WatchRecursively()) {
		// That shouldn't happen, since we only watch the ancestors in this
		// case.
		return true;
	}
 
	Node* directoryNode = _GetNode(entryRef.DirectoryNodeRef());
	if (directoryNode == NULL)
		return true;
 
	Directory* directory = directoryNode->ToDirectory();
	if (directory == NULL) {
		// We're out of sync with reality.
		if (!dryRun) {
			if (Entry* nodeEntry = directory->FirstNodeEntry()) {
				// remove the entry that is in the way and re-add the proper
				// entry
				NotOwningEntryRef directoryEntryRef = nodeEntry->EntryRef();
				BString directoryName = nodeEntry->Name();
				_DeleteEntry(nodeEntry, true);
				_EntryCreated(directoryEntryRef, entryRef.DirectoryNodeRef(),
					true, false, notify, NULL);
			} else {
				// It's either the base node or something's severely fishy.
				// Resync the whole path.
				_StopWatchingAncestors(fBaseAncestor, true);
				_StartWatchingAncestors(fBaseAncestor, true);
			}
		}
 
		return false;
	}
 
	// Check, if there's a colliding entry.
	if (Entry* nodeEntry = directory->FindEntry(entryRef.name)) {
		Node* entryNode = nodeEntry->Node();
		if (entryNode != NULL && entryNode->NodeRef() == nodeRef)
			return true;
 
		// We're out of sync with reality -- the new entry refers to a different
		// node.
		_DeleteEntry(nodeEntry, true);
	}
 
	if (dryRun)
		return true;
 
	_AddEntryIfNeeded(directory, entryRef.name, nodeRef, isDirectory, notify,
		_entry);
	return true;
}
 
 
bool
PathHandler::_EntryRemoved(const NotOwningEntryRef& entryRef,
	const node_ref& nodeRef, bool dryRun, bool notify, Entry** _keepEntry)
{
	if (_keepEntry != NULL)
		*_keepEntry = NULL;
 
	Ancestor* ancestor = _GetAncestor(nodeRef);
	if (ancestor != NULL) {
		// The node is an ancestor. If this is a true match, stop watching the
		// ancestor.
		if (!ancestor->Exists())
			return true;
 
		if (entryRef != ancestor->EntryRef()) {
			// We might be out of sync with reality -- the new entry refers to a
			// different node.
			struct stat ancestorStat;
			if (BEntry(&ancestor->EntryRef()).GetStat(&ancestorStat) != B_OK) {
				if (!dryRun)
					_StopWatchingAncestors(ancestor, true);
				return false;
			}
 
			if (node_ref(ancestorStat.st_dev, ancestorStat.st_ino)
					!= ancestor->NodeRef()
				|| S_ISDIR(ancestorStat.st_mode) != ancestor->IsDirectory()) {
				if (!dryRun) {
					_StopWatchingAncestors(ancestor, true);
					_StartWatchingAncestors(ancestor, true);
				}
				return false;
			}
			return true;
		}
 
		if (!dryRun)
			_StopWatchingAncestors(ancestor, true);
		return false;
	}
 
	ancestor = _GetAncestor(entryRef.DirectoryNodeRef());
	if (ancestor != NULL) {
		if (ancestor != fBaseAncestor) {
			// The directory is a true ancestor -- the notification cannot be
			// of interest, since the node didn't match a known ancestor.
			return true;
		}
 
		// The directory is our path. If watching recursively, just fall
		// through. Otherwise, we want to pass on the notification, if directory
		// watching is enabled.
		if (!_WatchRecursively()) {
			if (notify && (fFlags & B_WATCH_DIRECTORY) != 0) {
				_NotifyEntryCreatedOrRemoved(entryRef, nodeRef,
					make_path(fPath, entryRef.name), false, B_ENTRY_REMOVED);
					// We don't know whether this was a directory, but it
					// doesn't matter in this case.
			}
			return true;
		}
	}
 
	if (!_WatchRecursively()) {
		// That shouldn't happen, since we only watch the ancestors in this
		// case.
		return true;
	}
 
	Node* directoryNode = _GetNode(entryRef.DirectoryNodeRef());
	if (directoryNode == NULL) {
		// We shouldn't get a notification, if we don't known the directory.
		return true;
	}
 
	Directory* directory = directoryNode->ToDirectory();
	if (directory == NULL) {
		// We might be out of sync with reality or the notification is just
		// late. The former case is extremely unlikely (we are watching the node
		// and its parent directory after all) and rather hard to verify.
		return true;
	}
 
	Entry* nodeEntry = directory->FindEntry(entryRef.name);
	if (nodeEntry == NULL) {
		// might be a non-directory node while we're in directories-only mode
		return true;
	}
 
	if (!dryRun) {
		if (_keepEntry != NULL)
			*_keepEntry = nodeEntry;
		else
			_DeleteEntry(nodeEntry, notify);
	}
	return true;
}
 
 
bool
PathHandler::_CheckDuplicateEntryNotification(int32 opcode,
	const entry_ref& toEntryRef, const node_ref& nodeRef,
	const entry_ref* fromEntryRef)
{
	if (opcode == fDuplicateEntryNotificationOpcode
		&& nodeRef == fDuplicateEntryNotificationNodeRef
		&& toEntryRef == fDuplicateEntryNotificationToEntryRef
		&& (fromEntryRef == NULL
			|| *fromEntryRef == fDuplicateEntryNotificationFromEntryRef)) {
		return true;
	}
 
	fDuplicateEntryNotificationOpcode = opcode;
	fDuplicateEntryNotificationNodeRef = nodeRef;
	fDuplicateEntryNotificationToEntryRef = toEntryRef;
	fDuplicateEntryNotificationFromEntryRef = fromEntryRef != NULL
		? *fromEntryRef : entry_ref();
	return false;
}
 
 
void
PathHandler::_UnsetDuplicateEntryNotification()
{
	fDuplicateEntryNotificationOpcode = B_STAT_CHANGED;
	fDuplicateEntryNotificationNodeRef = node_ref();
	fDuplicateEntryNotificationFromEntryRef = entry_ref();
	fDuplicateEntryNotificationToEntryRef = entry_ref();
}
 
 
Ancestor*
PathHandler::_GetAncestor(const node_ref& nodeRef) const
{
	return fAncestors.Lookup(nodeRef);
}
 
 
status_t
PathHandler::_AddNode(const node_ref& nodeRef, bool isDirectory, bool notify,
	Entry* entry, Node** _node)
{
	TRACE("%p->PathHandler::_AddNode(%" B_PRIdDEV ":%" B_PRIdINO
		", isDirectory: %d, notify: %d)\n", this, nodeRef.device, nodeRef.node,
		isDirectory, notify);
 
	// If hard links are supported, we may already know the node.
	Node* node = _GetNode(nodeRef);
	if (node != NULL) {
		if (entry != NULL) {
			entry->SetNode(node);
			node->AddNodeEntry(entry);
		}
 
		if (_node != NULL)
			*_node = node;
		return B_OK;
	}
 
	// create the node
	Directory* directoryNode = NULL;
	if (isDirectory)
		node = directoryNode = Directory::Create(nodeRef);
	else
		node = new(std::nothrow) Node(nodeRef);
 
	if (node == NULL)
		return B_NO_MEMORY;
 
	ObjectDeleter<Node> nodeDeleter(node);
 
	// start watching (don't do that for the base node, since we watch it
	// already via fBaseAncestor)
	if (nodeRef != fBaseAncestor->NodeRef()) {
		uint32 flags = (fFlags & WATCH_NODE_FLAG_MASK) | B_WATCH_DIRECTORY;
		status_t error = sWatchingInterface->WatchNode(&nodeRef, flags, this);
		if (error != B_OK)
			return error;
	}
 
	fNodes.Insert(nodeDeleter.Detach());
 
	if (entry != NULL) {
		entry->SetNode(node);
		node->AddNodeEntry(entry);
	}
 
	if (_node != NULL)
		*_node = node;
 
	if (!isDirectory)
		return B_OK;
 
	// recursively add the directory's descendents
	BDirectory directory;
	if (directory.SetTo(&nodeRef) != B_OK) {
		if (_node != NULL)
			*_node = node;
		return B_OK;
	}
 
	entry_ref entryRef;
	while (directory.GetNextRef(&entryRef) == B_OK) {
		struct stat st;
		if (BEntry(&entryRef).GetStat(&st) != B_OK)
			continue;
 
		bool isDirectory = S_ISDIR(st.st_mode);
		status_t error = _AddEntryIfNeeded(directoryNode, entryRef.name,
			node_ref(st.st_dev, st.st_ino), isDirectory, notify);
		if (error != B_OK) {
			TRACE("%p->PathHandler::_AddNode(%" B_PRIdDEV ":%" B_PRIdINO
				", isDirectory: %d, notify: %d): failed to add directory "
				"entry: \"%s\"\n", this, nodeRef.device, nodeRef.node,
				isDirectory, notify, entryRef.name);
			continue;
		}
	}
 
	return B_OK;
}
 
 
void
PathHandler::_DeleteNode(Node* node, bool notify)
{
	if (Directory* directory = node->ToDirectory()) {
		Entry* entry = directory->RemoveAllEntries();
		while (entry != NULL) {
			Entry* nextEntry = entry->HashNext();
			_DeleteEntryAlreadyRemovedFromParent(entry, notify);
			entry = nextEntry;
		}
	}
 
	if (node->NodeRef() != fBaseAncestor->NodeRef())
		sWatchingInterface->WatchNode(&node->NodeRef(), B_STOP_WATCHING, this);
 
	fNodes.Remove(node);
	delete node;
}
 
 
Node*
PathHandler::_GetNode(const node_ref& nodeRef) const
{
	return fNodes.Lookup(nodeRef);
}
 
 
status_t
PathHandler::_AddEntryIfNeeded(Directory* directory, const char* name,
	const node_ref& nodeRef, bool isDirectory, bool notify,
	Entry** _entry)
{
	TRACE("%p->PathHandler::_AddEntryIfNeeded(%" B_PRIdDEV ":%" B_PRIdINO
		":\"%s\", %" B_PRIdDEV ":%" B_PRIdINO
		", isDirectory: %d, notify: %d)\n", this, directory->NodeRef().device,
		directory->NodeRef().node, name, nodeRef.device, nodeRef.node,
		isDirectory, notify);
 
	if (!isDirectory && _WatchDirectoriesOnly()) {
		if (_entry != NULL)
			*_entry = NULL;
		return B_OK;
	}
 
	Entry* entry = directory->CreateEntry(name, NULL);
	if (entry == NULL)
		return B_NO_MEMORY;
 
	status_t error = _AddNode(nodeRef, isDirectory, notify && _WatchFilesOnly(),
		entry);
	if (error != B_OK) {
		directory->RemoveEntry(entry);
		delete entry;
		return error;
	}
 
	if (notify)
		_NotifyEntryCreatedOrRemoved(entry, B_ENTRY_CREATED);
 
	if (_entry != NULL)
		*_entry = entry;
	return B_OK;
}
 
 
void
PathHandler::_DeleteEntry(Entry* entry, bool notify)
{
	entry->Parent()->RemoveEntry(entry);
	_DeleteEntryAlreadyRemovedFromParent(entry, notify);
}
 
 
void
PathHandler::_DeleteEntryAlreadyRemovedFromParent(Entry* entry, bool notify)
{
	if (notify)
		_NotifyEntryCreatedOrRemoved(entry, B_ENTRY_REMOVED);
 
	Node* node = entry->Node();
	if (node->IsOnlyNodeEntry(entry))
		_DeleteNode(node, notify && _WatchFilesOnly());
 
	delete entry;
}
 
 
void
PathHandler::_NotifyFilesCreatedOrRemoved(Entry* entry, int32 opcode) const
{
	Directory* directory = entry->Node()->ToDirectory();
	if (directory == NULL) {
		_NotifyEntryCreatedOrRemoved(entry, opcode);
		return;
	}
 
	for (EntryMap::Iterator it = directory->GetEntryIterator(); it.HasNext();)
		_NotifyFilesCreatedOrRemoved(it.Next(), opcode);
}
 
 
void
PathHandler::_NotifyEntryCreatedOrRemoved(Entry* entry, int32 opcode) const
{
	Node* node = entry->Node();
	_NotifyEntryCreatedOrRemoved(
		NotOwningEntryRef(entry->Parent()->NodeRef(), entry->Name()),
		node->NodeRef(), _EntryPath(entry), node->IsDirectory(), opcode);
}
 
 
void
PathHandler::_NotifyEntryCreatedOrRemoved(const entry_ref& entryRef,
	const node_ref& nodeRef, const char* path, bool isDirectory, int32 opcode)
	const
{
	if (isDirectory ? _WatchFilesOnly() : _WatchDirectoriesOnly())
		return;
 
	TRACE("%p->PathHandler::_NotifyEntryCreatedOrRemoved(): entry %s: %"
		B_PRIdDEV ":%" B_PRIdINO ":\"%s\", node: %" B_PRIdDEV ":%" B_PRIdINO
		"\n", this, opcode == B_ENTRY_CREATED ? "created" : "removed",
		entryRef.device, entryRef.directory, entryRef.name, nodeRef.device,
		nodeRef.node);
 
	BMessage message(B_PATH_MONITOR);
	message.AddInt32("opcode", opcode);
	message.AddInt32("device", entryRef.device);
	message.AddInt64("directory", entryRef.directory);
	message.AddInt32("node device", nodeRef.device);
		// This field is not in a usual node monitoring message, since the node
		// the created/removed entry refers to always belongs to the same FS as
		// the directory, as another FS cannot yet/no longer be mounted there.
		// In our case, however, this can very well be the case, e.g. when the
		// the notification is triggered in response to a directory tree having
		// been moved into/out of our path.
	message.AddInt64("node", nodeRef.node);
	message.AddString("name", entryRef.name);
 
	_NotifyTarget(message, path);
}
 
 
void
PathHandler::_NotifyEntryMoved(const entry_ref& fromEntryRef,
	const entry_ref& toEntryRef, const node_ref& nodeRef, const char* fromPath,
	const char* path, bool isDirectory, bool wasAdded, bool wasRemoved) const
{
	if ((isDirectory && _WatchFilesOnly())
		|| (!isDirectory && _WatchDirectoriesOnly())) {
		return;
	}
 
	TRACE("%p->PathHandler::_NotifyEntryMoved(): entry: %" B_PRIdDEV ":%"
		B_PRIdINO ":\"%s\" -> %" B_PRIdDEV ":%" B_PRIdINO ":\"%s\", node: %"
		B_PRIdDEV ":%" B_PRIdINO "\n", this, fromEntryRef.device,
		fromEntryRef.directory, fromEntryRef.name, toEntryRef.device,
		toEntryRef.directory, toEntryRef.name, nodeRef.device, nodeRef.node);
 
	BMessage message(B_PATH_MONITOR);
	message.AddInt32("opcode", B_ENTRY_MOVED);
	message.AddInt32("device", fromEntryRef.device);
	message.AddInt64("from directory", fromEntryRef.directory);
	message.AddInt64("to directory", toEntryRef.directory);
	message.AddInt32("node device", nodeRef.device);
	message.AddInt64("node", nodeRef.node);
	message.AddString("from name", fromEntryRef.name);
	message.AddString("name", toEntryRef.name);
 
	if (wasAdded)
		message.AddBool("added", true);
	if (wasRemoved)
		message.AddBool("removed", true);
 
	if (fromPath != NULL && fromPath[0] != '\0')
		message.AddString("from path", fromPath);
 
	_NotifyTarget(message, path);
}
 
 
void
PathHandler::_NotifyTarget(BMessage& message, const char* path) const
{
	message.what = B_PATH_MONITOR;
	if (path != NULL && path[0] != '\0')
		message.AddString("path", path);
	message.AddString("watched_path", fPath.String());
	fTarget.SendMessage(&message);
}
 
 
 
BString
PathHandler::_NodePath(const Node* node) const
{
	if (Entry* entry = node->FirstNodeEntry())
		return _EntryPath(entry);
	return node == fBaseNode ? fPath : BString();
}
 
 
BString
PathHandler::_EntryPath(const Entry* entry) const
{
	return make_path(_NodePath(entry->Parent()), entry->Name());
}
 
 
bool
PathHandler::_WatchRecursively() const
{
	return (fFlags & B_WATCH_RECURSIVELY) != 0;
}
 
 
bool
PathHandler::_WatchFilesOnly() const
{
	return (fFlags & B_WATCH_FILES_ONLY) != 0;
}
 
 
bool
PathHandler::_WatchDirectoriesOnly() const
{
	return (fFlags & B_WATCH_DIRECTORIES_ONLY) != 0;
}
 
 
} // namespace
 
 
//	#pragma mark - BPathMonitor
 
 
namespace BPrivate {
 
 
BPathMonitor::BPathMonitor()
{
}
 
 
BPathMonitor::~BPathMonitor()
{
}
 
 
/*static*/ status_t
BPathMonitor::StartWatching(const char* path, uint32 flags,
	const BMessenger& target)
{
	TRACE("BPathMonitor::StartWatching(%s, %" B_PRIx32 ")\n", path, flags);
 
	if (path == NULL || path[0] == '\0')
		return B_BAD_VALUE;
 
	// B_WATCH_FILES_ONLY and B_WATCH_DIRECTORIES_ONLY are mutual exclusive
	if ((flags & B_WATCH_FILES_ONLY) != 0
		&& (flags & B_WATCH_DIRECTORIES_ONLY) != 0) {
		return B_BAD_VALUE;
	}
 
	status_t status = _InitIfNeeded();
	if (status != B_OK)
		return status;
 
	BAutolock _(sLooper);
 
	Watcher* watcher = sWatchers->Lookup(target);
	bool newWatcher = false;
	if (watcher != NULL) {
		// If there's already a handler for the path, we'll replace it, but
		// add its flags.
		if (PathHandler* handler = watcher->Lookup(path)) {
			// keep old flags save for conflicting mutually exclusive ones
			uint32 oldFlags = handler->Flags();
			const uint32 kMutuallyExclusiveFlags
				= B_WATCH_FILES_ONLY | B_WATCH_DIRECTORIES_ONLY;
			if ((flags & kMutuallyExclusiveFlags) != 0)
				oldFlags &= ~(uint32)kMutuallyExclusiveFlags;
			flags |= oldFlags;
 
			watcher->Remove(handler);
			handler->Quit();
		}
	} else {
		watcher = Watcher::Create(target);
		if (watcher == NULL)
			return B_NO_MEMORY;
		sWatchers->Insert(watcher);
		newWatcher = true;
	}
 
	PathHandler* handler = new (std::nothrow) PathHandler(path, flags, target,
		sLooper);
	status = handler != NULL ? handler->InitCheck() : B_NO_MEMORY;
 
	if (status != B_OK) {
		if (handler != NULL)
			handler->Quit();
 
		if (newWatcher) {
			sWatchers->Remove(watcher);
			delete watcher;
		}
		return status;
	}
 
	watcher->Insert(handler);
	return B_OK;
}
 
 
/*static*/ status_t
BPathMonitor::StopWatching(const char* path, const BMessenger& target)
{
	if (sLooper == NULL)
		return B_BAD_VALUE;
 
	TRACE("BPathMonitor::StopWatching(%s)\n", path);
 
	BAutolock _(sLooper);
 
	Watcher* watcher = sWatchers->Lookup(target);
	if (watcher == NULL)
		return B_BAD_VALUE;
 
	PathHandler* handler = watcher->Lookup(path);
	if (handler == NULL)
		return B_BAD_VALUE;
 
	watcher->Remove(handler);
	handler->Quit();
 
	if (watcher->IsEmpty()) {
		sWatchers->Remove(watcher);
		delete watcher;
	}
 
	return B_OK;
}
 
 
/*static*/ status_t
BPathMonitor::StopWatching(const BMessenger& target)
{
	if (sLooper == NULL)
		return B_BAD_VALUE;
 
	BAutolock _(sLooper);
 
	Watcher* watcher = sWatchers->Lookup(target);
	if (watcher == NULL)
		return B_BAD_VALUE;
 
	// delete handlers
	PathHandler* handler = watcher->Clear(true);
	while (handler != NULL) {
		PathHandler* nextHandler = handler->HashNext();
		handler->Quit();
		handler = nextHandler;
	}
 
	sWatchers->Remove(watcher);
	delete watcher;
 
	return B_OK;
}
 
 
/*static*/ void
BPathMonitor::SetWatchingInterface(BWatchingInterface* watchingInterface)
{
	sWatchingInterface = watchingInterface != NULL
		? watchingInterface : sDefaultWatchingInterface;
}
 
 
/*static*/ status_t
BPathMonitor::_InitIfNeeded()
{
	pthread_once(&sInitOnce, &BPathMonitor::_Init);
	return sLooper != NULL ? B_OK : B_NO_MEMORY;
}
 
 
/*static*/ void
BPathMonitor::_Init()
{
	sDefaultWatchingInterface = new(std::nothrow) BWatchingInterface;
	if (sDefaultWatchingInterface == NULL)
		return;
 
	sWatchers = new(std::nothrow) WatcherMap;
	if (sWatchers == NULL || sWatchers->Init() != B_OK)
		return;
 
	if (sWatchingInterface == NULL)
		SetWatchingInterface(sDefaultWatchingInterface);
 
	BLooper* looper = new (std::nothrow) BLooper("PathMonitor looper");
	TRACE("Start PathMonitor looper\n");
	if (looper == NULL)
		return;
	thread_id thread = looper->Run();
	if (thread < 0) {
		delete looper;
		return;
	}
 
	sLooper = looper;
}
 
 
// #pragma mark - BWatchingInterface
 
 
BPathMonitor::BWatchingInterface::BWatchingInterface()
{
}
 
 
BPathMonitor::BWatchingInterface::~BWatchingInterface()
{
}
 
 
status_t
BPathMonitor::BWatchingInterface::WatchNode(const node_ref* node, uint32 flags,
	const BMessenger& target)
{
	return watch_node(node, flags, target);
}
 
 
status_t
BPathMonitor::BWatchingInterface::WatchNode(const node_ref* node, uint32 flags,
	const BHandler* handler, const BLooper* looper)
{
	return watch_node(node, flags, handler, looper);
}
 
 
status_t
BPathMonitor::BWatchingInterface::StopWatching(const BMessenger& target)
{
	return stop_watching(target);
}
 
 
status_t
BPathMonitor::BWatchingInterface::StopWatching(const BHandler* handler,
	const BLooper* looper)
{
	return stop_watching(handler, looper);
}
 
 
}	// namespace BPrivate

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fHashNext.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fHashNext.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fHashNext.

V522 Dereferencing of the null pointer 'directory' might take place.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fHashNext.