Esempio n. 1
0
/*static*/ status_t
BPathMonitor::StopWatching(const char* path, BMessenger target)
{
	if (sLocker == NULL)
		return B_NO_INIT;

	TRACE("StopWatching(%s)\n", path);

	BAutolock _(sLocker);

	WatcherMap::iterator iterator = sWatchers.find(target);
	if (iterator == sWatchers.end())
		return B_BAD_VALUE;

	Watcher* watcher = iterator->second;
	HandlerMap::iterator i = watcher->handlers.find(path);

	if (i == watcher->handlers.end())
		return B_BAD_VALUE;

	PathHandler* handler = i->second;
	watcher->handlers.erase(i);

	handler->Quit();

	if (watcher->handlers.empty()) {
		sWatchers.erase(iterator);
		delete watcher;
	}

	return B_OK;
}
Esempio n. 2
0
/*static*/ status_t
BPathMonitor::StopWatching(BMessenger target)
{
	if (sLocker == NULL)
		return B_NO_INIT;

	BAutolock _(sLocker);

	WatcherMap::iterator iterator = sWatchers.find(target);
	if (iterator == sWatchers.end())
		return B_BAD_VALUE;

	Watcher* watcher = iterator->second;
	while (!watcher->handlers.empty()) {
		HandlerMap::iterator i = watcher->handlers.begin();
		PathHandler* handler = i->second;
		watcher->handlers.erase(i);

		handler->Quit();
	}

	sWatchers.erase(iterator);
	delete watcher;

	return B_OK;
}
Esempio n. 3
0
void FileWatcher::pollAll() {
	// check each file:
	WatcherMap::iterator it = gWatchedFiles.begin();
	while (it != gWatchedFiles.end()) {
		it->second.test();
		it++;
	}
}
Esempio n. 4
0
FileWatcher::~FileWatcher(){ 
	// check each file:
	WatcherMap::iterator it = gWatchedFiles.begin();
	while (it != gWatchedFiles.end()) {
		it->second.remove(this);
		it++;
	}
}
Esempio n. 5
0
/*static*/ status_t
BPathMonitor::StartWatching(const char* path, uint32 flags, BMessenger target)
{
	TRACE("StartWatching(%s)\n", path);

	status_t status = _InitLockerIfNeeded();
	if (status != B_OK)
		return status;

	// use the global looper for receiving node monitor notifications
	status = _InitLooperIfNeeded();
	if (status < B_OK)
		return status;

	BAutolock _(sLocker);

	WatcherMap::iterator iterator = sWatchers.find(target);
	Watcher* watcher = NULL;
	if (iterator != sWatchers.end())
		watcher = iterator->second;

	PathHandler* handler = new (nothrow) PathHandler(path, flags, target,
		sLooper);
	if (handler == NULL)
		return B_NO_MEMORY;
	status = handler->InitCheck();
	if (status < B_OK) {
		delete handler;
		return status;
	}

	if (watcher == NULL) {
		watcher = new (nothrow) BPrivate::Watcher;
		if (watcher == NULL) {
			delete handler;
			return B_NO_MEMORY;
		}
		sWatchers[target] = watcher;
	}

	watcher->handlers[path] = handler;
	return B_OK;
}
Esempio n. 6
0
namespace BPrivate {

struct FileEntry {
	entry_ref	ref;
	ino_t		node;
};

#if __GNUC__ > 3
	bool operator<(const FileEntry& a, const FileEntry& b);
	class FileEntryLess : public binary_function<FileEntry, FileEntry, bool>
	{
		public:
		bool operator() (const FileEntry& a, const FileEntry& b) const
		{
			return a < b;
		}
	};
	typedef set<FileEntry, FileEntryLess> FileSet;
#else
	typedef set<FileEntry> FileSet;
#endif

struct WatchedDirectory {
	node_ref		node;
	bool			contained;
};
typedef set<WatchedDirectory> DirectorySet;


class PathHandler;
typedef map<BString, PathHandler*> HandlerMap;

struct Watcher {
	HandlerMap handlers;
};
typedef map<BMessenger, Watcher*> WatcherMap;

class PathHandler : public BHandler {
	public:
		PathHandler(const char* path, uint32 flags, BMessenger target,
			BLooper* looper);
		virtual ~PathHandler();

		status_t InitCheck() const;
		void SetTarget(BMessenger target);
		void Quit();

		virtual void MessageReceived(BMessage* message);
#ifdef TRACE_PATH_MONITOR
		void Dump();
#endif

	private:
		status_t _GetClosest(const char* path, bool updatePath,
			node_ref& nodeRef);

		bool _WatchRecursively() const;
		bool _WatchFilesOnly() const;
		bool _WatchFoldersOnly() const;

		void _EntryCreated(BMessage* message);
		void _EntryRemoved(BMessage* message);
		void _EntryMoved(BMessage* message);

		bool _IsContained(const node_ref& nodeRef) const;
		bool _IsContained(BEntry& entry) const;
		bool _HasDirectory(const node_ref& nodeRef,
			bool* _contained = NULL) const;
		bool _CloserToPath(BEntry& entry) const;

		void _NotifyTarget(BMessage* message) const;
		void _NotifyTarget(BMessage* message, const node_ref& nodeRef) const;

		status_t _AddDirectory(BEntry& entry, bool notify = false);
		status_t _AddDirectory(node_ref& nodeRef, bool notify = false);
		status_t _RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode);
		status_t _RemoveDirectory(BEntry& entry, ino_t directoryNode);

		bool _HasFile(const node_ref& nodeRef) const;
		status_t _AddFile(BEntry& entry, bool notify = false);
		status_t _RemoveFile(const node_ref& nodeRef);
		status_t _RemoveFile(BEntry& entry);

		BPath			fPath;
		int32			fPathLength;
		BMessenger		fTarget;
		uint32			fFlags;
		status_t		fStatus;
		DirectorySet	fDirectories;
		FileSet			fFiles;
};


static WatcherMap sWatchers;
static BLocker* sLocker = NULL;
static BLooper* sLooper = NULL;


static status_t
set_entry(const node_ref& nodeRef, const char* name, BEntry& entry)
{
	entry_ref ref;
	ref.device = nodeRef.device;
	ref.directory = nodeRef.node;

	status_t status = ref.set_name(name);
	if (status != B_OK)
		return status;

	return entry.SetTo(&ref, true);
}


bool
operator<(const FileEntry& a, const FileEntry& b)
{
	if (a.ref.device == b.ref.device && a.node < b.node)
		return true;
	if (a.ref.device < b.ref.device)
		return true;

	return false;
}


bool
operator<(const node_ref& a, const node_ref& b)
{
	if (a.device == b.device && a.node < b.node)
		return true;
	if (a.device < b.device)
		return true;

	return false;
}


bool
operator<(const WatchedDirectory& a, const WatchedDirectory& b)
{
	return a.node < b.node;
}


//	#pragma mark -


PathHandler::PathHandler(const char* path, uint32 flags, BMessenger target,
		BLooper* looper)
	: BHandler(path),
	fTarget(target),
	fFlags(flags)
{
	if (path == NULL || !path[0]) {
		fStatus = B_BAD_VALUE;
		return;
	}

	// TODO: support watching not-yet-mounted volumes as well!
	node_ref nodeRef;
	fStatus = _GetClosest(path, true, nodeRef);
	if (fStatus < B_OK)
		return;

	TRACE("PathHandler: %s\n", path);

	looper->Lock();
	looper->AddHandler(this);
	looper->Unlock();

	fStatus = _AddDirectory(nodeRef);

	// TODO: work-around for existing files (should not watch the directory in
	// this case)
	BEntry entry(path);
	if (entry.Exists() && !entry.IsDirectory())
		_AddFile(entry);
}


PathHandler::~PathHandler()
{
}


status_t
PathHandler::InitCheck() const
{
	return fStatus;
}


void
PathHandler::Quit()
{
	if (sLooper->Lock()) {
		stop_watching(this);
		sLooper->RemoveHandler(this);
		sLooper->Unlock();
	}
	delete this;
}


#ifdef TRACE_PATH_MONITOR
void
PathHandler::Dump()
{
	TRACE("WATCHING DIRECTORIES:\n");
	DirectorySet::iterator i = fDirectories.begin();
	for (; i != fDirectories.end(); i++) {
		TRACE("  %ld:%Ld (%s)\n", i->node.device, i->node.node, i->contained
			? "contained" : "-");
	}

	TRACE("WATCHING FILES:\n");

	FileSet::iterator j = fFiles.begin();
	for (; j != fFiles.end(); j++) {
		TRACE("  %ld:%Ld\n", j->ref.device, j->node);
	}
}
#endif


status_t
PathHandler::_GetClosest(const char* path, bool updatePath, node_ref& nodeRef)
{
	BPath first(path);
	BString missing;

	while (true) {
		// try to find the first part of the path that exists
		BDirectory directory;
		status_t status = directory.SetTo(first.Path());
		if (status == B_OK) {
			status = directory.GetNodeRef(&nodeRef);
			if (status == B_OK) {
				if (updatePath) {
					// normalize path
					status = fPath.SetTo(&directory, NULL, true);
					if (status == B_OK) {
						fPath.Append(missing.String());
						fPathLength = strlen(fPath.Path());
					}
				}
				return status;
			}
		}

		if (updatePath) {
			if (missing.Length() > 0)
				missing.Prepend("/");
			missing.Prepend(first.Leaf());
		}

		if (first.GetParent(&first) != B_OK)
			return B_ERROR;
	}
}


bool
PathHandler::_WatchRecursively() const
{
	return (fFlags & B_WATCH_RECURSIVELY) != 0;
}


bool
PathHandler::_WatchFilesOnly() const
{
	return (fFlags & B_WATCH_FILES_ONLY) != 0;
}


bool
PathHandler::_WatchFoldersOnly() const
{
	return (fFlags & B_WATCH_FOLDERS_ONLY) != 0;
}


void
PathHandler::_EntryCreated(BMessage* message)
{
	const char* name;
	node_ref nodeRef;
	if (message->FindInt32("device", &nodeRef.device) != B_OK
		|| message->FindInt64("directory", &nodeRef.node) != B_OK
		|| message->FindString("name", &name) != B_OK) {
		TRACE("PathHandler::_EntryCreated() - malformed message!\n");
		return;
	}

	BEntry entry;
	if (set_entry(nodeRef, name, entry) != B_OK) {
		TRACE("PathHandler::_EntryCreated() - set_entry failed!\n");
		return;
	}

	bool parentContained = false;
	bool entryContained = _IsContained(entry);
	if (entryContained)
		parentContained = _IsContained(nodeRef);
	bool notify = entryContained;

	if (entry.IsDirectory()) {
		// ignore the directory if it's already known
		if (entry.GetNodeRef(&nodeRef) == B_OK
			&& _HasDirectory(nodeRef)) {
			TRACE("    WE ALREADY HAVE DIR %s, %ld:%Ld\n",
				name, nodeRef.device, nodeRef.node);
			return;
		}

		// a new directory to watch for us
		if ((!entryContained && !_CloserToPath(entry))
			|| (parentContained && !_WatchRecursively())
			|| _AddDirectory(entry, true) != B_OK
			|| _WatchFilesOnly())
			notify = parentContained;
		// NOTE: entry is now toast after _AddDirectory() was called!
		// Does not matter right now, but if it's a problem, use the node_ref
		// version...
	} else if (entryContained) {
		TRACE("  NEW ENTRY PARENT CONTAINED: %d\n", parentContained);
		_AddFile(entry);
	}

	if (notify && entryContained) {
		message->AddBool("added", true);
		// nodeRef is pointing to the parent directory
		entry.GetNodeRef(&nodeRef);
		_NotifyTarget(message, nodeRef);
	}
}


void
PathHandler::_EntryRemoved(BMessage* message)
{
	node_ref nodeRef;
	uint64 directoryNode;
	if (message->FindInt32("device", &nodeRef.device) != B_OK
		|| message->FindInt64("directory", (int64 *)&directoryNode) != B_OK
		|| message->FindInt64("node", &nodeRef.node) != B_OK)
		return;

	bool contained;
	if (_HasDirectory(nodeRef, &contained)) {
		// the directory has been removed, so we remove it as well
		_RemoveDirectory(nodeRef, directoryNode);
		if (contained && !_WatchFilesOnly()) {
			message->AddBool("removed", true);
			_NotifyTarget(message, nodeRef);
		}
	} else if (_HasFile(nodeRef)) {
		message->AddBool("removed", true);
		_NotifyTarget(message, nodeRef);
		_RemoveFile(nodeRef);
	}
}


void
PathHandler::_EntryMoved(BMessage* message)
{
	// has the entry been moved into a monitored directory or has
	// it been removed from one?
	const char* name;
	node_ref nodeRef;
	uint64 fromNode;
	uint64 node;
	if (message->FindInt32("device", &nodeRef.device) != B_OK
		|| message->FindInt64("to directory", &nodeRef.node) != B_OK
		|| message->FindInt64("from directory", (int64 *)&fromNode) != B_OK
		|| message->FindInt64("node", (int64 *)&node) != B_OK
		|| message->FindString("name", &name) != B_OK)
		return;

	BEntry entry;
	if (set_entry(nodeRef, name, entry) != B_OK)
		return;

	bool entryContained = _IsContained(entry);
	bool wasAdded = false;
	bool wasRemoved = false;
	bool notify = false;

	bool parentContained;
	if (_HasDirectory(nodeRef, &parentContained)) {
		// something has been added to our watched directories

		nodeRef.node = node;
		TRACE("    ADDED TO PARENT (%d), has entry %d/%d, entry %d %d\n",
			parentContained, _HasDirectory(nodeRef), _HasFile(nodeRef),
			entryContained, _CloserToPath(entry));

		if (entry.IsDirectory()) {
			if (!_HasDirectory(nodeRef)
				&& (entryContained || _CloserToPath(entry))) {
				// there is a new directory to watch for us
				if (entryContained
					|| (parentContained && !_WatchRecursively())) {
					_AddDirectory(entry, true);
					// NOTE: entry is toast now!
				} else if (_GetClosest(fPath.Path(), false,
						nodeRef) == B_OK) {
					// the new directory might put us even
					// closer to the path we are after
					_AddDirectory(nodeRef, true);
				}

				wasAdded = true;
				notify = entryContained;
			}
			if (_WatchFilesOnly())
				notify = false;
		} else if (!_HasFile(nodeRef) && entryContained) {
			// file has been added
			wasAdded = true;
			notify = true;
			_AddFile(entry);
		}
	} else {
		// and entry has been removed from our directories
		wasRemoved = true;

		nodeRef.node = node;
		if (entry.IsDirectory()) {
			if (_HasDirectory(nodeRef, &notify))
				_RemoveDirectory(entry, fromNode);
			if (_WatchFilesOnly())
				notify = false;
		} else {
			_RemoveFile(entry);
			notify = true;
		}
	}

	if (notify) {
		if (wasAdded)
			message->AddBool("added", true);
		if (wasRemoved)
			message->AddBool("removed", true);

		_NotifyTarget(message, nodeRef);
	}
}


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:
					_NotifyTarget(message);
					break;
			}
			break;
		}

		default:
			BHandler::MessageReceived(message);
			break;
	}

//#ifdef TRACE_PATH_MONITOR
//	Dump();
//#endif
}


bool
PathHandler::_IsContained(const node_ref& nodeRef) const
{
	BDirectory directory(&nodeRef);
	if (directory.InitCheck() != B_OK)
		return false;

	BEntry entry;
	if (directory.GetEntry(&entry) != B_OK)
		return false;

	return _IsContained(entry);
}


bool
PathHandler::_IsContained(BEntry& entry) const
{
	BPath path;
	if (entry.GetPath(&path) != B_OK)
		return false;

	bool contained = strncmp(path.Path(), fPath.Path(), fPathLength) == 0;
	if (!contained)
		return false;

	// Prevent the case that the entry is in another folder which happens
	// to have the same substring for fPathLength chars, like:
	// /path/we/are/watching
	// /path/we/are/watching-not/subfolder/entry
	// NOTE: We wouldn't be here if path.Path() was shorter than fPathLength,
	// strncmp() catches that case.
	const char* last = &path.Path()[fPathLength];
	if (last[0] && last[0] != '/')
		return false;

	return true;
}


bool
PathHandler::_HasDirectory(const node_ref& nodeRef,
	bool* _contained /* = NULL */) const
{
	WatchedDirectory directory;
	directory.node = nodeRef;

	DirectorySet::const_iterator iterator = fDirectories.find(directory);
	if (iterator == fDirectories.end())
		return false;

	if (_contained != NULL)
		*_contained = iterator->contained;
	return true;
}


bool
PathHandler::_CloserToPath(BEntry& entry) const
{
	BPath path;
	if (entry.GetPath(&path) != B_OK)
		return false;

	return strncmp(path.Path(), fPath.Path(), strlen(path.Path())) == 0;
}


void
PathHandler::_NotifyTarget(BMessage* message) const
{
	// NOTE: This version is only used for B_STAT_CHANGED and B_ATTR_CHANGED
	node_ref nodeRef;
	if (message->FindInt32("device", &nodeRef.device) != B_OK
		|| message->FindInt64("node", &nodeRef.node) != B_OK)
		return;
	_NotifyTarget(message, nodeRef);
}


void
PathHandler::_NotifyTarget(BMessage* message, const node_ref& nodeRef) const
{
	BMessage update(*message);
	update.what = B_PATH_MONITOR;

	TRACE("_NotifyTarget(): node ref %ld.%Ld\n", nodeRef.device, nodeRef.node);

	WatchedDirectory directory;
	directory.node = nodeRef;

	DirectorySet::const_iterator iterator = fDirectories.find(directory);
	if (iterator != fDirectories.end()) {
		if (_WatchFilesOnly()) {
			// stat or attr notification for a directory
			return;
		}
		BDirectory nodeDirectory(&nodeRef);
		BEntry entry;
		if (nodeDirectory.GetEntry(&entry) == B_OK) {
			BPath path(&entry);
			update.AddString("path", path.Path());
		}
	} else {
		if (_WatchFoldersOnly()) {
			// this is bound to be a notification for a file
			return;
		}
		FileEntry setEntry;
		setEntry.ref.device = nodeRef.device;
		setEntry.node = nodeRef.node;
			// name does not need to be set, since it's not used for comparing
		FileSet::const_iterator i = fFiles.find(setEntry);
		if (i != fFiles.end()) {
			BPath path(&(i->ref));
			update.AddString("path", path.Path());
		}
	}

	// This is in case the target is interested in figuring out which
	// BPathMonitor::StartWatching() call the message is resulting from.
	update.AddString("watched_path", fPath.Path());

	fTarget.SendMessage(&update);
}


status_t
PathHandler::_AddDirectory(BEntry& entry, bool notify)
{
	WatchedDirectory directory;
	status_t status = entry.GetNodeRef(&directory.node);
	if (status != B_OK)
		return status;

#ifdef TRACE_PATH_MONITOR
{
	BPath path(&entry);
	TRACE("  ADD DIRECTORY %s, %ld:%Ld\n",
		path.Path(), directory.node.device, directory.node.node);
}
#endif

	// check if we are already know this directory

	// TODO: It should be possible to ommit this check if we know it
	// can't be the case (for example when adding subfolders recursively,
	// although in that case, the API user may still have added this folder
	// independently, so for now, it should be the safest to perform this
	// check in all cases.)
	if (_HasDirectory(directory.node))
		return B_OK;

	directory.contained = _IsContained(entry);

	uint32 flags;
	if (directory.contained)
		flags = (fFlags & WATCH_NODE_FLAG_MASK) | B_WATCH_DIRECTORY;
	else
		flags = B_WATCH_DIRECTORY;

	status = watch_node(&directory.node, flags, this);
	if (status != B_OK)
		return status;

	fDirectories.insert(directory);

	if (_WatchRecursively()) {
		BDirectory dir(&directory.node);
		while (dir.GetNextEntry(&entry) == B_OK) {
			if (entry.IsDirectory()) {
				// and here is the recursion:
				if (_AddDirectory(entry, notify) != B_OK)
					break;
			} else if (!_WatchFoldersOnly()) {
				if (_AddFile(entry, notify) != B_OK)
					break;
			}
		}
	}

#if 0
	BEntry parent;
	if (entry.GetParent(&parent) == B_OK
		&& !_IsContained(parent)) {
		// TODO: remove parent from watched directories
	}
#endif
	return B_OK;
}


status_t
PathHandler::_AddDirectory(node_ref& nodeRef, bool notify)
{
	BDirectory directory(&nodeRef);
	status_t status = directory.InitCheck();
	if (status == B_OK) {
		BEntry entry;
		status = directory.GetEntry(&entry);
		if (status == B_OK)
			status = _AddDirectory(entry, notify);
	}

	return status;
}


status_t
PathHandler::_RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode)
{
	TRACE("  REMOVE DIRECTORY %ld:%Ld\n", nodeRef.device, nodeRef.node);

	WatchedDirectory directory;
	directory.node = nodeRef;

	DirectorySet::iterator iterator = fDirectories.find(directory);
	if (iterator == fDirectories.end())
		return B_ENTRY_NOT_FOUND;

	watch_node(&directory.node, B_STOP_WATCHING, this);

	node_ref directoryRef;
	directoryRef.device = nodeRef.device;
	directoryRef.node = directoryNode;

	if (!_HasDirectory(directoryRef)) {
		// we don't have the parent directory now, but we'll need it in order
		// to find this directory again in case it's added again
		if (_AddDirectory(directoryRef) != B_OK
			&& _GetClosest(fPath.Path(), false, directoryRef) == B_OK)
			_AddDirectory(directoryRef);
	}

	fDirectories.erase(iterator);

	// TODO: stop watching subdirectories and their files when in recursive
	// mode!

	return B_OK;
}


status_t
PathHandler::_RemoveDirectory(BEntry& entry, ino_t directoryNode)
{
	node_ref nodeRef;
	status_t status = entry.GetNodeRef(&nodeRef);
	if (status != B_OK)
		return status;

	return _RemoveDirectory(nodeRef, directoryNode);
}


bool
PathHandler::_HasFile(const node_ref& nodeRef) const
{
	FileEntry setEntry;
	setEntry.ref.device = nodeRef.device;
	setEntry.node = nodeRef.node;
		// name does not need to be set, since it's not used for comparing
	FileSet::const_iterator iterator = fFiles.find(setEntry);
	return iterator != fFiles.end();
}


status_t
PathHandler::_AddFile(BEntry& entry, bool notify)
{
	if ((fFlags & (WATCH_NODE_FLAG_MASK & ~B_WATCH_DIRECTORY)) == 0)
		return B_OK;

#ifdef TRACE_PATH_MONITOR
{
	BPath path(&entry);
	TRACE("  ADD FILE %s\n", path.Path());
}
#endif

	node_ref nodeRef;
	status_t status = entry.GetNodeRef(&nodeRef);
	if (status != B_OK)
		return status;

	// check if we already know this file

	// TODO: It should be possible to omit this check if we know it
	// can't be the case (for example when adding subfolders recursively,
	// although in that case, the API user may still have added this file
	// independently, so for now, it should be the safest to perform this
	// check in all cases.)
	if (_HasFile(nodeRef))
		return B_OK;

	status = watch_node(&nodeRef, (fFlags & WATCH_NODE_FLAG_MASK), this);
	if (status != B_OK)
		return status;

	FileEntry setEntry;
	entry.GetRef(&setEntry.ref);
	setEntry.node = nodeRef.node;

	fFiles.insert(setEntry);

	if (notify && _WatchFilesOnly()) {
		// We also notify our target about new files if it's only interested
		// in files; it won't be notified about new directories, so it cannot
		// know when to search for them.
		BMessage update;
		update.AddInt32("opcode", B_ENTRY_CREATED);
		update.AddInt32("device", nodeRef.device);
		update.AddInt64("directory", setEntry.ref.directory);
		update.AddString("name", setEntry.ref.name);
		update.AddBool("added", true);

		_NotifyTarget(&update, nodeRef);
	}

	return B_OK;
}


status_t
PathHandler::_RemoveFile(const node_ref& nodeRef)
{
	TRACE("  REMOVE FILE %ld:%Ld\n", nodeRef.device, nodeRef.node);

	FileEntry setEntry;
	setEntry.ref.device = nodeRef.device;
	setEntry.node = nodeRef.node;
		// name does not need to be set, since it's not used for comparing
	FileSet::iterator iterator = fFiles.find(setEntry);
	if (iterator == fFiles.end())
		return B_ENTRY_NOT_FOUND;

	watch_node(&nodeRef, B_STOP_WATCHING, this);
	fFiles.erase(iterator);
	return B_OK;
}


status_t
PathHandler::_RemoveFile(BEntry& entry)
{
	node_ref nodeRef;
	status_t status = entry.GetNodeRef(&nodeRef);
	if (status != B_OK)
		return status;

	return _RemoveFile(nodeRef);
}


//	#pragma mark -


BPathMonitor::BPathMonitor()
{
}


BPathMonitor::~BPathMonitor()
{
}


/*static*/ status_t
BPathMonitor::_InitLockerIfNeeded()
{
	static vint32 lock = 0;

	if (sLocker != NULL)
		return B_OK;

	while (sLocker == NULL) {
		if (atomic_add(&lock, 1) == 0) {
			sLocker = new (nothrow) BLocker("path monitor");
			TRACE("Create PathMonitor locker\n");
			if (sLocker == NULL)
				return B_NO_MEMORY;
		}
		snooze(5000);
	}

	return B_OK;
}


/*static*/ status_t
BPathMonitor::_InitLooperIfNeeded()
{
	static vint32 lock = 0;

	if (sLooper != NULL)
		return B_OK;

	while (sLooper == NULL) {
		if (atomic_add(&lock, 1) == 0) {
			// first thread initializes the global looper
			sLooper = new (nothrow) BLooper("PathMonitor looper");
			TRACE("Start PathMonitor looper\n");
			if (sLooper == NULL)
				return B_NO_MEMORY;
			thread_id thread = sLooper->Run();
			if (thread < B_OK)
				return (status_t)thread;
		}
		snooze(5000);
	}

	return sLooper->Thread() >= 0 ? B_OK : B_ERROR;
}


/*static*/ status_t
BPathMonitor::StartWatching(const char* path, uint32 flags, BMessenger target)
{
	TRACE("StartWatching(%s)\n", path);

	status_t status = _InitLockerIfNeeded();
	if (status != B_OK)
		return status;

	// use the global looper for receiving node monitor notifications
	status = _InitLooperIfNeeded();
	if (status < B_OK)
		return status;

	BAutolock _(sLocker);

	WatcherMap::iterator iterator = sWatchers.find(target);
	Watcher* watcher = NULL;
	if (iterator != sWatchers.end())
		watcher = iterator->second;

	PathHandler* handler = new (nothrow) PathHandler(path, flags, target,
		sLooper);
	if (handler == NULL)
		return B_NO_MEMORY;
	status = handler->InitCheck();
	if (status < B_OK) {
		delete handler;
		return status;
	}

	if (watcher == NULL) {
		watcher = new (nothrow) BPrivate::Watcher;
		if (watcher == NULL) {
			delete handler;
			return B_NO_MEMORY;
		}
		sWatchers[target] = watcher;
	}

	watcher->handlers[path] = handler;
	return B_OK;
}


/*static*/ status_t
BPathMonitor::StopWatching(const char* path, BMessenger target)
{
	if (sLocker == NULL)
		return B_NO_INIT;

	TRACE("StopWatching(%s)\n", path);

	BAutolock _(sLocker);

	WatcherMap::iterator iterator = sWatchers.find(target);
	if (iterator == sWatchers.end())
		return B_BAD_VALUE;

	Watcher* watcher = iterator->second;
	HandlerMap::iterator i = watcher->handlers.find(path);

	if (i == watcher->handlers.end())
		return B_BAD_VALUE;

	PathHandler* handler = i->second;
	watcher->handlers.erase(i);

	handler->Quit();

	if (watcher->handlers.empty()) {
		sWatchers.erase(iterator);
		delete watcher;
	}

	return B_OK;
}


/*static*/ status_t
BPathMonitor::StopWatching(BMessenger target)
{
	if (sLocker == NULL)
		return B_NO_INIT;

	BAutolock _(sLocker);

	WatcherMap::iterator iterator = sWatchers.find(target);
	if (iterator == sWatchers.end())
		return B_BAD_VALUE;

	Watcher* watcher = iterator->second;
	while (!watcher->handlers.empty()) {
		HandlerMap::iterator i = watcher->handlers.begin();
		PathHandler* handler = i->second;
		watcher->handlers.erase(i);

		handler->Quit();
	}

	sWatchers.erase(iterator);
	delete watcher;

	return B_OK;
}

}	// namespace BPrivate