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