xref: /haiku/src/kits/storage/PathMonitor.cpp (revision a381c8a06378de22ff08adf4282b4e3f7e50d250)
1 /*
2  * Copyright 2007, Haiku Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  */
8 
9 
10 #include <PathMonitor.h>
11 
12 #include <ObjectList.h>
13 
14 #include <Application.h>
15 #include <Autolock.h>
16 #include <Directory.h>
17 #include <Entry.h>
18 #include <Handler.h>
19 #include <Locker.h>
20 #include <Looper.h>
21 #include <Path.h>
22 #include <String.h>
23 
24 #include <map>
25 #include <set>
26 
27 #undef TRACE
28 //#define TRACE_PATH_MONITOR
29 #ifdef TRACE_PATH_MONITOR
30 #	define TRACE(x) printf x
31 #else
32 #	define TRACE(x) ;
33 #endif
34 
35 using namespace BPrivate;
36 using namespace std;
37 
38 
39 #define WATCH_NODE_FLAG_MASK	0x00ff
40 
41 namespace BPrivate {
42 
43 #if __GNUC__ > 3
44 	bool operator<(const node_ref& a, const node_ref& b);
45 	class node_ref_less : public binary_function<node_ref, node_ref, bool>
46 	{
47 		public:
48 		bool operator() (const node_ref& a, const node_ref& b) const
49 		{
50 			return a < b;
51 		}
52 	};
53 	typedef set<node_ref,node_ref_less> FileSet;
54 #else
55 	typedef set<node_ref> FileSet;
56 #endif
57 
58 struct watched_directory {
59 	node_ref	node;
60 	bool		contained;
61 };
62 typedef set<watched_directory> DirectorySet;
63 
64 
65 class PathHandler;
66 typedef map<BString, PathHandler*> HandlerMap;
67 
68 struct watcher {
69 	HandlerMap handlers;
70 };
71 typedef map<BMessenger, watcher*> WatcherMap;
72 
73 class PathHandler : public BHandler {
74 	public:
75 		PathHandler(const char* path, uint32 flags, BMessenger target);
76 		virtual ~PathHandler();
77 
78 		status_t InitCheck() const;
79 		void SetTarget(BMessenger target);
80 		void Quit();
81 
82 		virtual void MessageReceived(BMessage* message);
83 #ifdef TRACE_PATH_MONITOR
84 		void Dump();
85 #endif
86 
87 	private:
88 		status_t _GetClosest(const char* path, bool updatePath,
89 			node_ref& nodeRef);
90 		bool _WatchRecursively();
91 		bool _WatchFilesOnly();
92 		void _EntryCreated(BMessage* message);
93 		void _EntryRemoved(BMessage* message);
94 		void _EntryMoved(BMessage* message);
95 		bool _IsContained(const node_ref& nodeRef) const;
96 		bool _IsContained(BEntry& entry) const;
97 		bool _HasDirectory(const node_ref& nodeRef, bool* _contained = NULL) const;
98 		bool _CloserToPath(BEntry& entry) const;
99 		void _NotifyTarget(BMessage* message) const;
100 		status_t _AddDirectory(BEntry& entry);
101 		status_t _AddDirectory(node_ref& nodeRef);
102 		status_t _RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode);
103 		status_t _RemoveDirectory(BEntry& entry, ino_t directoryNode);
104 
105 		bool _HasFile(const node_ref& nodeRef) const;
106 		status_t _AddFile(BEntry& entry);
107 		status_t _RemoveFile(const node_ref& nodeRef);
108 		status_t _RemoveFile(BEntry& entry);
109 
110 		BPath			fPath;
111 		int32			fPathLength;
112 		BMessenger		fTarget;
113 		uint32			fFlags;
114 		status_t		fStatus;
115 		bool			fOwnsLooper;
116 		DirectorySet	fDirectories;
117 		FileSet			fFiles;
118 };
119 
120 
121 static WatcherMap sWatchers;
122 static BLocker* sLocker = NULL;
123 
124 
125 static status_t
126 set_entry(node_ref& nodeRef, const char* name, BEntry& entry)
127 {
128 	entry_ref ref;
129 	ref.device = nodeRef.device;
130 	ref.directory = nodeRef.node;
131 
132 	status_t status = ref.set_name(name);
133 	if (status != B_OK)
134 		return status;
135 
136 	return entry.SetTo(&ref, true);
137 }
138 
139 
140 bool
141 operator<(const node_ref& a, const node_ref& b)
142 {
143 	if (a.device < b.device)
144 		return true;
145 	if (a.device == b.device && a.node < b.node)
146 		return true;
147 
148 	return false;
149 }
150 
151 
152 bool
153 operator<(const watched_directory& a, const watched_directory& b)
154 {
155 	return a.node < b.node;
156 }
157 
158 
159 //	#pragma mark -
160 
161 
162 PathHandler::PathHandler(const char* path, uint32 flags, BMessenger target)
163 	: BHandler(path),
164 	fTarget(target),
165 	fFlags(flags),
166 	fOwnsLooper(false)
167 {
168 	if (path == NULL || !path[0]) {
169 		fStatus = B_BAD_VALUE;
170 		return;
171 	}
172 
173 	// TODO: support watching not-yet-mounted volumes as well!
174 	node_ref nodeRef;
175 	fStatus = _GetClosest(path, true, nodeRef);
176 	if (fStatus < B_OK)
177 		return;
178 
179 	BLooper* looper;
180 	// TODO: only have a single global looper!
181 	// TODO: Use BLooper::LooperForThread(find_looper(NULL)) ?
182 	looper = new BLooper("PathMonitor looper");
183 	looper->Run();
184 	fOwnsLooper = true;
185 
186 	looper->Lock();
187 	looper->AddHandler(this);
188 	looper->Unlock();
189 
190 	fStatus = _AddDirectory(nodeRef);
191 
192 	// TODO: work-around for existing files (should not watch the directory in this case)
193 	BEntry entry(path);
194 	if (entry.Exists() && !entry.IsDirectory())
195 		_AddFile(entry);
196 }
197 
198 
199 PathHandler::~PathHandler()
200 {
201 }
202 
203 
204 status_t
205 PathHandler::InitCheck() const
206 {
207 	return fStatus;
208 }
209 
210 
211 void
212 PathHandler::Quit()
213 {
214 	BMessenger me(this);
215 	me.SendMessage(B_QUIT_REQUESTED);
216 }
217 
218 
219 #ifdef TRACE_PATH_MONITOR
220 void
221 PathHandler::Dump()
222 {
223 	printf("WATCHING DIRECTORIES:\n");
224 	DirectorySet::iterator i = fDirectories.begin();
225 	for (; i != fDirectories.end(); i++) {
226 		printf("  %ld:%Ld (%s)\n", i->node.device, i->node.node, i->contained ? "contained" : "-");
227 	}
228 
229 	printf("WATCHING FILES:\n");
230 
231 	FileSet::iterator j = fFiles.begin();
232 	for (; j != fFiles.end(); j++) {
233 		printf("  %ld:%Ld\n", j->device, j->node);
234 	}
235 }
236 #endif
237 
238 
239 status_t
240 PathHandler::_GetClosest(const char* path, bool updatePath, node_ref& nodeRef)
241 {
242 	BPath first(path);
243 	BString missing;
244 
245 	while (true) {
246 		// try to find the first part of the path that exists
247 		BDirectory directory;
248 		status_t status = directory.SetTo(first.Path());
249 		if (status == B_OK) {
250 			status = directory.GetNodeRef(&nodeRef);
251 			if (status == B_OK) {
252 				if (updatePath) {
253 					// normalize path
254 					status = fPath.SetTo(&directory, NULL, true);
255 					if (status == B_OK) {
256 						fPath.Append(missing.String());
257 						fPathLength = strlen(fPath.Path());
258 					}
259 				}
260 				return status;
261 			}
262 		}
263 
264 		if (updatePath) {
265 			if (missing.Length() > 0)
266 				missing.Prepend("/");
267 			missing.Prepend(first.Leaf());
268 		}
269 
270 		if (first.GetParent(&first) != B_OK)
271 			return B_ERROR;
272 	}
273 }
274 
275 
276 bool
277 PathHandler::_WatchRecursively()
278 {
279 	return (fFlags & B_WATCH_RECURSIVELY) != 0;
280 }
281 
282 
283 bool
284 PathHandler::_WatchFilesOnly()
285 {
286 	return (fFlags & B_WATCH_FILES_ONLY) != 0;
287 }
288 
289 
290 void
291 PathHandler::_EntryCreated(BMessage* message)
292 {
293 	const char* name;
294 	node_ref nodeRef;
295 	if (message->FindInt32("device", &nodeRef.device) != B_OK
296 		|| message->FindInt64("directory", &nodeRef.node) != B_OK
297 		|| message->FindString("name", &name) != B_OK)
298 		return;
299 
300 	BEntry entry;
301 	if (set_entry(nodeRef, name, entry) != B_OK)
302 		return;
303 
304 	bool parentContained = false;
305 	bool entryContained = _IsContained(entry);
306 	if (entryContained)
307 		parentContained = _IsContained(nodeRef);
308 	bool notify = entryContained;
309 
310 	if (entry.IsDirectory()) {
311 		// ignore the directory if it's already known
312 		if (entry.GetNodeRef(&nodeRef) == B_OK
313 			&& _HasDirectory(nodeRef)) {
314 			TRACE(("    WE ALREADY HAVE DIR %s, %ld:%Ld\n",
315 				name, nodeRef.device, nodeRef.node));
316 			return;
317 		}
318 
319 		// a new directory to watch for us
320 		if (!entryContained && !_CloserToPath(entry)
321 			|| parentContained && !_WatchRecursively()
322 			|| _AddDirectory(entry) != B_OK
323 			|| _WatchFilesOnly())
324 			notify = parentContained;
325 	} else if (entryContained) {
326 		TRACE(("  NEW ENTRY PARENT CONTAINED: %d\n", parentContained));
327 		_AddFile(entry);
328 	}
329 
330 	if (notify && entryContained) {
331 		message->AddBool("added", true);
332 		_NotifyTarget(message);
333 	}
334 }
335 
336 
337 void
338 PathHandler::_EntryRemoved(BMessage* message)
339 {
340 	node_ref nodeRef;
341 	uint64 directoryNode;
342 	if (message->FindInt32("device", &nodeRef.device) != B_OK
343 		|| message->FindInt64("directory", (int64 *)&directoryNode) != B_OK
344 		|| message->FindInt64("node", &nodeRef.node) != B_OK)
345 		return;
346 
347 	bool notify = false;
348 
349 	if (_HasDirectory(nodeRef, &notify)) {
350 		// the directory has been removed, so we remove it as well
351 		_RemoveDirectory(nodeRef, directoryNode);
352 		if (_WatchFilesOnly())
353 			notify = false;
354 	} else if (_HasFile(nodeRef)) {
355 		_RemoveFile(nodeRef);
356 		notify = true;
357 	}
358 
359 	if (notify) {
360 		message->AddBool("removed", true);
361 		_NotifyTarget(message);
362 	}
363 }
364 
365 
366 void
367 PathHandler::_EntryMoved(BMessage* message)
368 {
369 	// has the entry been moved into a monitored directory or has
370 	// it been removed from one?
371 	const char* name;
372 	node_ref nodeRef;
373 	uint64 fromNode;
374 	uint64 node;
375 	if (message->FindInt32("device", &nodeRef.device) != B_OK
376 		|| message->FindInt64("to directory", &nodeRef.node) != B_OK
377 		|| message->FindInt64("from directory", (int64 *)&fromNode) != B_OK
378 		|| message->FindInt64("node", (int64 *)&node) != B_OK
379 		|| message->FindString("name", &name) != B_OK)
380 		return;
381 
382 	BEntry entry;
383 	if (set_entry(nodeRef, name, entry) != B_OK)
384 		return;
385 
386 	bool entryContained = _IsContained(entry);
387 	bool wasAdded = false;
388 	bool wasRemoved = false;
389 	bool notify = false;
390 
391 	bool parentContained;
392 	if (_HasDirectory(nodeRef, &parentContained)) {
393 		// something has been added to our watched directories
394 
395 		nodeRef.node = node;
396 		TRACE(("    ADDED TO PARENT (%d), has entry %d/%d, entry %d %d\n",
397 			parentContained, _HasDirectory(nodeRef), _HasFile(nodeRef),
398 			entryContained, _CloserToPath(entry)));
399 
400 		if (entry.IsDirectory()) {
401 			if (!_HasDirectory(nodeRef)
402 				&& (entryContained || _CloserToPath(entry))) {
403 				// there is a new directory to watch for us
404 				if (entryContained
405 					|| parentContained && !_WatchRecursively())
406 					_AddDirectory(entry);
407 				else if (_GetClosest(fPath.Path(), false,
408 						nodeRef) == B_OK) {
409 					// the new directory might put us even
410 					// closer to the path we are after
411 					_AddDirectory(nodeRef);
412 				}
413 
414 				wasAdded = true;
415 				notify = entryContained;
416 			}
417 			if (_WatchFilesOnly())
418 				notify = false;
419 		} else if (!_HasFile(nodeRef) && entryContained) {
420 			// file has been added
421 			wasAdded = true;
422 			notify = true;
423 			_AddFile(entry);
424 		}
425 	} else {
426 		// and entry has been removed from our directories
427 		wasRemoved = true;
428 
429 		nodeRef.node = node;
430 		if (entry.IsDirectory()) {
431 			if (_HasDirectory(nodeRef, &notify))
432 				_RemoveDirectory(entry, fromNode);
433 			if (_WatchFilesOnly())
434 				notify = false;
435 		} else {
436 			_RemoveFile(entry);
437 			notify = true;
438 		}
439 	}
440 
441 	if (notify) {
442 		if (wasAdded)
443 			message->AddBool("added", true);
444 		if (wasRemoved)
445 			message->AddBool("removed", true);
446 
447 		_NotifyTarget(message);
448 	}
449 }
450 
451 
452 void
453 PathHandler::MessageReceived(BMessage* message)
454 {
455 	switch (message->what) {
456 		case B_NODE_MONITOR:
457 		{
458 			int32 opcode;
459 			if (message->FindInt32("opcode", &opcode) != B_OK)
460 				return;
461 
462 			switch (opcode) {
463 				case B_ENTRY_CREATED:
464 					_EntryCreated(message);
465 					break;
466 
467 				case B_ENTRY_REMOVED:
468 					_EntryRemoved(message);
469 					break;
470 
471 				case B_ENTRY_MOVED:
472 					_EntryMoved(message);
473 					break;
474 
475 				default:
476 					_NotifyTarget(message);
477 					break;
478 			}
479 			break;
480 		}
481 
482 		case B_QUIT_REQUESTED:
483 		{
484 			BLooper* looper = Looper();
485 
486 			stop_watching(this);
487 			looper->RemoveHandler(this);
488 			delete this;
489 
490 			if (fOwnsLooper)
491 				looper->Quit();
492 
493 			return;
494 		}
495 
496 		default:
497 			BHandler::MessageReceived(message);
498 			break;
499 	}
500 
501 #ifdef TRACE_PATH_MONITOR
502 	Dump();
503 #endif
504 }
505 
506 
507 bool
508 PathHandler::_IsContained(const node_ref& nodeRef) const
509 {
510 	BDirectory directory(&nodeRef);
511 	if (directory.InitCheck() != B_OK)
512 		return false;
513 
514 	BEntry entry;
515 	if (directory.GetEntry(&entry) != B_OK)
516 		return false;
517 
518 	return _IsContained(entry);
519 }
520 
521 
522 bool
523 PathHandler::_IsContained(BEntry& entry) const
524 {
525 	BPath path;
526 	if (entry.GetPath(&path) != B_OK)
527 		return false;
528 
529 	bool contained = strncmp(path.Path(), fPath.Path(), fPathLength) == 0;
530 	if (!contained)
531 		return false;
532 
533 	const char* last = &path.Path()[fPathLength];
534 	if (last[0] && last[0] != '/')
535 		return false;
536 
537 	return true;
538 }
539 
540 
541 bool
542 PathHandler::_HasDirectory(const node_ref& nodeRef, bool* _contained /* = NULL */) const
543 {
544 	watched_directory directory;
545 	directory.node = nodeRef;
546 
547 	DirectorySet::const_iterator iterator = fDirectories.find(directory);
548 	if (iterator == fDirectories.end())
549 		return false;
550 
551 	if (_contained != NULL)
552 		*_contained = iterator->contained;
553 	return true;
554 }
555 
556 
557 bool
558 PathHandler::_CloserToPath(BEntry& entry) const
559 {
560 	BPath path;
561 	if (entry.GetPath(&path) != B_OK)
562 		return false;
563 
564 	return strncmp(path.Path(), fPath.Path(), strlen(path.Path())) == 0;
565 }
566 
567 
568 void
569 PathHandler::_NotifyTarget(BMessage* message) const
570 {
571 	BMessage update(*message);
572 	update.what = B_PATH_MONITOR;
573 	update.AddString("path", fPath.Path());
574 
575 	fTarget.SendMessage(&update);
576 }
577 
578 
579 status_t
580 PathHandler::_AddDirectory(BEntry& entry)
581 {
582 	watched_directory directory;
583 	status_t status = entry.GetNodeRef(&directory.node);
584 	if (status != B_OK)
585 		return status;
586 
587 #ifdef TRACE_PATH_MONITOR
588 {
589 	BPath path(&entry);
590 	printf("  ADD DIRECTORY %s, %ld:%Ld\n",
591 		path.Path(), directory.node.device, directory.node.node);
592 }
593 #endif
594 
595 	// check if we are already know this directory
596 
597 	if (_HasDirectory(directory.node))
598 		return B_OK;
599 
600 	directory.contained = _IsContained(entry);
601 
602 	uint32 flags;
603 	if (directory.contained)
604 		flags = (fFlags & WATCH_NODE_FLAG_MASK) | B_WATCH_DIRECTORY;
605 	else
606 		flags = B_WATCH_DIRECTORY;
607 
608 	status = watch_node(&directory.node, flags, this);
609 	if (status != B_OK)
610 		return status;
611 
612 	fDirectories.insert(directory);
613 
614 #if 0
615 	BEntry parent;
616 	if (entry.GetParent(&parent) == B_OK
617 		&& !_IsContained(parent)) {
618 		// TODO: remove parent from watched directories
619 	}
620 #endif
621 	return B_OK;
622 }
623 
624 
625 status_t
626 PathHandler::_AddDirectory(node_ref& nodeRef)
627 {
628 	BDirectory directory(&nodeRef);
629 	status_t status = directory.InitCheck();
630 	if (status == B_OK) {
631 		BEntry entry;
632 		status = directory.GetEntry(&entry);
633 		if (status == B_OK)
634 			status = _AddDirectory(entry);
635 	}
636 
637 	return status;
638 }
639 
640 
641 status_t
642 PathHandler::_RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode)
643 {
644 	TRACE(("  REMOVE DIRECTORY %ld:%Ld\n", nodeRef.device, nodeRef.node));
645 
646 	watched_directory directory;
647 	directory.node = nodeRef;
648 
649 	DirectorySet::iterator iterator = fDirectories.find(directory);
650 	if (iterator == fDirectories.end())
651 		return B_ENTRY_NOT_FOUND;
652 
653 	watch_node(&directory.node, B_STOP_WATCHING, this);
654 
655 	node_ref directoryRef;
656 	directoryRef.device = nodeRef.device;
657 	directoryRef.node = directoryNode;
658 
659 	if (!_HasDirectory(directoryRef)) {
660 		// we don't have the parent directory now, but we'll need it in order
661 		// to find this directory again in case it's added again
662 		if (_AddDirectory(directoryRef) != B_OK
663 			&& _GetClosest(fPath.Path(), false, directoryRef) == B_OK)
664 			_AddDirectory(directoryRef);
665 	}
666 
667 	fDirectories.erase(iterator);
668 	return B_OK;
669 }
670 
671 
672 status_t
673 PathHandler::_RemoveDirectory(BEntry& entry, ino_t directoryNode)
674 {
675 	node_ref nodeRef;
676 	status_t status = entry.GetNodeRef(&nodeRef);
677 	if (status != B_OK)
678 		return status;
679 
680 	return _RemoveDirectory(nodeRef, directoryNode);
681 }
682 
683 
684 bool
685 PathHandler::_HasFile(const node_ref& nodeRef) const
686 {
687 	FileSet::const_iterator iterator = fFiles.find(nodeRef);
688 	return iterator != fFiles.end();
689 }
690 
691 
692 status_t
693 PathHandler::_AddFile(BEntry& entry)
694 {
695 	if ((fFlags & (WATCH_NODE_FLAG_MASK & ~B_WATCH_DIRECTORY)) == 0)
696 		return B_OK;
697 
698 #ifdef TRACE_PATH_MONITOR
699 {
700 	BPath path(&entry);
701 	printf("  ADD FILE %s\n", path.Path());
702 }
703 #endif
704 
705 	node_ref nodeRef;
706 	status_t status = entry.GetNodeRef(&nodeRef);
707 	if (status != B_OK)
708 		return status;
709 
710 	// check if we are already know this file
711 
712 	if (_HasFile(nodeRef))
713 		return B_OK;
714 
715 	status = watch_node(&nodeRef, (fFlags & WATCH_NODE_FLAG_MASK), this);
716 	if (status != B_OK)
717 		return status;
718 
719 	fFiles.insert(nodeRef);
720 	return B_OK;
721 }
722 
723 
724 status_t
725 PathHandler::_RemoveFile(const node_ref& nodeRef)
726 {
727 	TRACE(("  REMOVE FILE %ld:%Ld\n", nodeRef.device, nodeRef.node));
728 
729 	FileSet::iterator iterator = fFiles.find(nodeRef);
730 	if (iterator == fFiles.end())
731 		return B_ENTRY_NOT_FOUND;
732 
733 	watch_node(&nodeRef, B_STOP_WATCHING, this);
734 	fFiles.erase(iterator);
735 	return B_OK;
736 }
737 
738 
739 status_t
740 PathHandler::_RemoveFile(BEntry& entry)
741 {
742 	node_ref nodeRef;
743 	status_t status = entry.GetNodeRef(&nodeRef);
744 	if (status != B_OK)
745 		return status;
746 
747 	return _RemoveFile(nodeRef);
748 }
749 
750 
751 //	#pragma mark -
752 
753 
754 BPathMonitor::BPathMonitor()
755 {
756 }
757 
758 
759 BPathMonitor::~BPathMonitor()
760 {
761 }
762 
763 
764 /*static*/ status_t
765 BPathMonitor::_InitIfNeeded()
766 {
767 	static vint32 lock = 0;
768 
769 	if (sLocker != NULL)
770 		return B_OK;
771 
772 	while (sLocker == NULL) {
773 		if (atomic_add(&lock, 1) == 0) {
774 			sLocker = new BLocker("path monitor");
775 		}
776 		snooze(5000);
777 	}
778 
779 	return B_OK;
780 }
781 
782 
783 /*static*/ status_t
784 BPathMonitor::StartWatching(const char* path, uint32 flags, BMessenger target)
785 {
786 	status_t status = _InitIfNeeded();
787 	if (status != B_OK)
788 		return status;
789 
790 	BAutolock _(sLocker);
791 
792 	WatcherMap::iterator iterator = sWatchers.find(target);
793 	struct watcher* watcher = NULL;
794 	if (iterator != sWatchers.end())
795 		watcher = iterator->second;
796 
797 	PathHandler* handler = new PathHandler(path, flags, target);
798 	status = handler->InitCheck();
799 	if (status < B_OK)
800 		return status;
801 
802 	if (watcher == NULL) {
803 		watcher = new BPrivate::watcher;
804 		sWatchers[target] = watcher;
805 	}
806 
807 	watcher->handlers[path] = handler;
808 	return B_OK;
809 }
810 
811 
812 /*static*/ status_t
813 BPathMonitor::StopWatching(const char* path, BMessenger target)
814 {
815 	BAutolock _(sLocker);
816 
817 	WatcherMap::iterator iterator = sWatchers.find(target);
818 	if (iterator == sWatchers.end())
819 		return B_BAD_VALUE;
820 
821 	struct watcher* watcher = iterator->second;
822 	HandlerMap::iterator i = watcher->handlers.find(path);
823 
824 	if (i == watcher->handlers.end())
825 		return B_BAD_VALUE;
826 
827 	PathHandler* handler = i->second;
828 	watcher->handlers.erase(i);
829 
830 	if (handler->LockLooper())
831 		handler->Quit();
832 
833 	if (watcher->handlers.empty()) {
834 		sWatchers.erase(iterator);
835 		delete watcher;
836 	}
837 
838 	return B_OK;
839 }
840 
841 
842 /*static*/ status_t
843 BPathMonitor::StopWatching(BMessenger target)
844 {
845 	BAutolock _(sLocker);
846 
847 	WatcherMap::iterator iterator = sWatchers.find(target);
848 	if (iterator == sWatchers.end())
849 		return B_BAD_VALUE;
850 
851 	struct watcher* watcher = iterator->second;
852 	while (!watcher->handlers.empty()) {
853 		HandlerMap::iterator i = watcher->handlers.begin();
854 		PathHandler* handler = i->second;
855 		watcher->handlers.erase(i);
856 
857 		if (handler->LockLooper())
858 			handler->Quit();
859 	}
860 
861 	sWatchers.erase(iterator);
862 	delete watcher;
863 
864 	return B_OK;
865 }
866 
867 }	// namespace BPrivate
868