xref: /haiku/src/kits/storage/PathMonitor.cpp (revision f75a7bf508f3156d63a14f8fd77c5e0ca4d08c42)
1 /*
2  * Copyright 2007-2008, 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  *		Stephan Aßmus <superstippi@gmx.de>
8  */
9 
10 
11 #include <PathMonitor.h>
12 
13 #include <ObjectList.h>
14 
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 <new>
26 #include <set>
27 
28 #undef TRACE
29 //#define TRACE_PATH_MONITOR
30 #ifdef TRACE_PATH_MONITOR
31 #	define TRACE(x...) printf(x)
32 #else
33 #	define TRACE(x...) ;
34 #endif
35 
36 using namespace BPrivate;
37 using namespace std;
38 using std::nothrow; // TODO: Remove this line if the above line is enough.
39 
40 // TODO: Use optimizations where stuff is already known to avoid iterating
41 // the watched directory and files set too often.
42 
43 #define WATCH_NODE_FLAG_MASK	0x00ff
44 
45 namespace BPrivate {
46 
47 struct FileEntry {
48 	entry_ref	ref;
49 	ino_t		node;
50 };
51 
52 #if __GNUC__ > 3
53 	bool operator<(const FileEntry& a, const FileEntry& b);
54 	class FileEntryLess : public binary_function<FileEntry, FileEntry, bool>
55 	{
56 		public:
57 		bool operator() (const FileEntry& a, const FileEntry& b) const
58 		{
59 			return a < b;
60 		}
61 	};
62 	typedef set<FileEntry, FileEntryLess> FileSet;
63 #else
64 	typedef set<FileEntry> FileSet;
65 #endif
66 
67 struct WatchedDirectory {
68 	node_ref		node;
69 	bool			contained;
70 };
71 typedef set<WatchedDirectory> DirectorySet;
72 
73 
74 class PathHandler;
75 typedef map<BString, PathHandler*> HandlerMap;
76 
77 struct Watcher {
78 	HandlerMap handlers;
79 };
80 typedef map<BMessenger, Watcher*> WatcherMap;
81 
82 class PathHandler : public BHandler {
83 	public:
84 		PathHandler(const char* path, uint32 flags, BMessenger target,
85 			BLooper* looper);
86 		virtual ~PathHandler();
87 
88 		status_t InitCheck() const;
89 		void SetTarget(BMessenger target);
90 		void Quit();
91 
92 		virtual void MessageReceived(BMessage* message);
93 #ifdef TRACE_PATH_MONITOR
94 		void Dump();
95 #endif
96 
97 	private:
98 		status_t _GetClosest(const char* path, bool updatePath,
99 			node_ref& nodeRef);
100 
101 		bool _WatchRecursively() const;
102 		bool _WatchFilesOnly() const;
103 		bool _WatchFoldersOnly() const;
104 
105 		void _EntryCreated(BMessage* message);
106 		void _EntryRemoved(BMessage* message);
107 		void _EntryMoved(BMessage* message);
108 
109 		bool _IsContained(const node_ref& nodeRef) const;
110 		bool _IsContained(BEntry& entry) const;
111 		bool _HasDirectory(const node_ref& nodeRef,
112 			bool* _contained = NULL) const;
113 		bool _CloserToPath(BEntry& entry) const;
114 
115 		void _NotifyTarget(BMessage* message) const;
116 		void _NotifyTarget(BMessage* message, const node_ref& nodeRef) const;
117 
118 		status_t _AddDirectory(BEntry& entry);
119 		status_t _AddDirectory(node_ref& nodeRef);
120 		status_t _RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode);
121 		status_t _RemoveDirectory(BEntry& entry, ino_t directoryNode);
122 
123 		bool _HasFile(const node_ref& nodeRef) const;
124 		status_t _AddFile(BEntry& entry);
125 		status_t _RemoveFile(const node_ref& nodeRef);
126 		status_t _RemoveFile(BEntry& entry);
127 
128 		BPath			fPath;
129 		int32			fPathLength;
130 		BMessenger		fTarget;
131 		uint32			fFlags;
132 		status_t		fStatus;
133 		DirectorySet	fDirectories;
134 		FileSet			fFiles;
135 };
136 
137 
138 static WatcherMap sWatchers;
139 static BLocker* sLocker = NULL;
140 static BLooper* sLooper = NULL;
141 
142 
143 static status_t
144 set_entry(const node_ref& nodeRef, const char* name, BEntry& entry)
145 {
146 	entry_ref ref;
147 	ref.device = nodeRef.device;
148 	ref.directory = nodeRef.node;
149 
150 	status_t status = ref.set_name(name);
151 	if (status != B_OK)
152 		return status;
153 
154 	return entry.SetTo(&ref, true);
155 }
156 
157 
158 bool
159 operator<(const FileEntry& a, const FileEntry& b)
160 {
161 	if (a.ref.device == b.ref.device && a.node < b.node)
162 		return true;
163 	if (a.ref.device < b.ref.device)
164 		return true;
165 
166 	return false;
167 }
168 
169 
170 bool
171 operator<(const node_ref& a, const node_ref& b)
172 {
173 	if (a.device == b.device && a.node < b.node)
174 		return true;
175 	if (a.device < b.device)
176 		return true;
177 
178 	return false;
179 }
180 
181 
182 bool
183 operator<(const WatchedDirectory& a, const WatchedDirectory& b)
184 {
185 	return a.node < b.node;
186 }
187 
188 
189 //	#pragma mark -
190 
191 
192 PathHandler::PathHandler(const char* path, uint32 flags, BMessenger target,
193 		BLooper* looper)
194 	: BHandler(path),
195 	fTarget(target),
196 	fFlags(flags)
197 {
198 	if (path == NULL || !path[0]) {
199 		fStatus = B_BAD_VALUE;
200 		return;
201 	}
202 
203 	// TODO: support watching not-yet-mounted volumes as well!
204 	node_ref nodeRef;
205 	fStatus = _GetClosest(path, true, nodeRef);
206 	if (fStatus < B_OK)
207 		return;
208 
209 	looper->Lock();
210 	looper->AddHandler(this);
211 	looper->Unlock();
212 
213 	fStatus = _AddDirectory(nodeRef);
214 
215 	// TODO: work-around for existing files (should not watch the directory in
216 	// this case)
217 	BEntry entry(path);
218 	if (entry.Exists() && !entry.IsDirectory())
219 		_AddFile(entry);
220 }
221 
222 
223 PathHandler::~PathHandler()
224 {
225 }
226 
227 
228 status_t
229 PathHandler::InitCheck() const
230 {
231 	return fStatus;
232 }
233 
234 
235 void
236 PathHandler::Quit()
237 {
238 	if (sLooper->Lock()) {
239 		stop_watching(this);
240 		sLooper->RemoveHandler(this);
241 		sLooper->Unlock();
242 	}
243 	delete this;
244 }
245 
246 
247 #ifdef TRACE_PATH_MONITOR
248 void
249 PathHandler::Dump()
250 {
251 	printf("WATCHING DIRECTORIES:\n");
252 	DirectorySet::iterator i = fDirectories.begin();
253 	for (; i != fDirectories.end(); i++) {
254 		printf("  %ld:%Ld (%s)\n", i->node.device, i->node.node, i->contained
255 			? "contained" : "-");
256 	}
257 
258 	printf("WATCHING FILES:\n");
259 
260 	FileSet::iterator j = fFiles.begin();
261 	for (; j != fFiles.end(); j++) {
262 		printf("  %ld:%Ld\n", j->ref.device, j->node);
263 	}
264 }
265 #endif
266 
267 
268 status_t
269 PathHandler::_GetClosest(const char* path, bool updatePath, node_ref& nodeRef)
270 {
271 	BPath first(path);
272 	BString missing;
273 
274 	while (true) {
275 		// try to find the first part of the path that exists
276 		BDirectory directory;
277 		status_t status = directory.SetTo(first.Path());
278 		if (status == B_OK) {
279 			status = directory.GetNodeRef(&nodeRef);
280 			if (status == B_OK) {
281 				if (updatePath) {
282 					// normalize path
283 					status = fPath.SetTo(&directory, NULL, true);
284 					if (status == B_OK) {
285 						fPath.Append(missing.String());
286 						fPathLength = strlen(fPath.Path());
287 					}
288 				}
289 				return status;
290 			}
291 		}
292 
293 		if (updatePath) {
294 			if (missing.Length() > 0)
295 				missing.Prepend("/");
296 			missing.Prepend(first.Leaf());
297 		}
298 
299 		if (first.GetParent(&first) != B_OK)
300 			return B_ERROR;
301 	}
302 }
303 
304 
305 bool
306 PathHandler::_WatchRecursively() const
307 {
308 	return (fFlags & B_WATCH_RECURSIVELY) != 0;
309 }
310 
311 
312 bool
313 PathHandler::_WatchFilesOnly() const
314 {
315 	return (fFlags & B_WATCH_FILES_ONLY) != 0;
316 }
317 
318 
319 bool
320 PathHandler::_WatchFoldersOnly() const
321 {
322 	return (fFlags & B_WATCH_FOLDERS_ONLY) != 0;
323 }
324 
325 
326 void
327 PathHandler::_EntryCreated(BMessage* message)
328 {
329 	const char* name;
330 	node_ref nodeRef;
331 	if (message->FindInt32("device", &nodeRef.device) != B_OK
332 		|| message->FindInt64("directory", &nodeRef.node) != B_OK
333 		|| message->FindString("name", &name) != B_OK) {
334 		TRACE("PathHandler::_EntryCreated() - malformed message!\n");
335 		return;
336 	}
337 
338 	BEntry entry;
339 	if (set_entry(nodeRef, name, entry) != B_OK) {
340 		TRACE("PathHandler::_EntryCreated() - set_entry failed!\n");
341 		return;
342 	}
343 
344 	bool parentContained = false;
345 	bool entryContained = _IsContained(entry);
346 	if (entryContained)
347 		parentContained = _IsContained(nodeRef);
348 	bool notify = entryContained;
349 
350 	if (entry.IsDirectory()) {
351 		// ignore the directory if it's already known
352 		if (entry.GetNodeRef(&nodeRef) == B_OK
353 			&& _HasDirectory(nodeRef)) {
354 			TRACE("    WE ALREADY HAVE DIR %s, %ld:%Ld\n",
355 				name, nodeRef.device, nodeRef.node);
356 			return;
357 		}
358 
359 		// a new directory to watch for us
360 		if (!entryContained && !_CloserToPath(entry)
361 			|| parentContained && !_WatchRecursively()
362 			|| _AddDirectory(entry) != B_OK
363 			|| _WatchFilesOnly())
364 			notify = parentContained;
365 		// NOTE: entry is now toast after _AddDirectory() was called!
366 		// Does not matter right now, but if it's a problem, use the node_ref
367 		// version...
368 	} else if (entryContained) {
369 		TRACE("  NEW ENTRY PARENT CONTAINED: %d\n", parentContained);
370 		_AddFile(entry);
371 	}
372 
373 	if (notify && entryContained) {
374 		message->AddBool("added", true);
375 		// nodeRef is pointing to the parent directory
376 		entry.GetNodeRef(&nodeRef);
377 		_NotifyTarget(message, nodeRef);
378 	}
379 }
380 
381 
382 void
383 PathHandler::_EntryRemoved(BMessage* message)
384 {
385 	node_ref nodeRef;
386 	uint64 directoryNode;
387 	if (message->FindInt32("device", &nodeRef.device) != B_OK
388 		|| message->FindInt64("directory", (int64 *)&directoryNode) != B_OK
389 		|| message->FindInt64("node", &nodeRef.node) != B_OK)
390 		return;
391 
392 	bool contained;
393 	if (_HasDirectory(nodeRef, &contained)) {
394 		// the directory has been removed, so we remove it as well
395 		_RemoveDirectory(nodeRef, directoryNode);
396 		if (contained && !_WatchFilesOnly()) {
397 			message->AddBool("removed", true);
398 			_NotifyTarget(message, nodeRef);
399 		}
400 	} else if (_HasFile(nodeRef)) {
401 		message->AddBool("removed", true);
402 		_NotifyTarget(message, nodeRef);
403 		_RemoveFile(nodeRef);
404 	}
405 }
406 
407 
408 void
409 PathHandler::_EntryMoved(BMessage* message)
410 {
411 	// has the entry been moved into a monitored directory or has
412 	// it been removed from one?
413 	const char* name;
414 	node_ref nodeRef;
415 	uint64 fromNode;
416 	uint64 node;
417 	if (message->FindInt32("device", &nodeRef.device) != B_OK
418 		|| message->FindInt64("to directory", &nodeRef.node) != B_OK
419 		|| message->FindInt64("from directory", (int64 *)&fromNode) != B_OK
420 		|| message->FindInt64("node", (int64 *)&node) != B_OK
421 		|| message->FindString("name", &name) != B_OK)
422 		return;
423 
424 	BEntry entry;
425 	if (set_entry(nodeRef, name, entry) != B_OK)
426 		return;
427 
428 	bool entryContained = _IsContained(entry);
429 	bool wasAdded = false;
430 	bool wasRemoved = false;
431 	bool notify = false;
432 
433 	bool parentContained;
434 	if (_HasDirectory(nodeRef, &parentContained)) {
435 		// something has been added to our watched directories
436 
437 		nodeRef.node = node;
438 		TRACE("    ADDED TO PARENT (%d), has entry %d/%d, entry %d %d\n",
439 			parentContained, _HasDirectory(nodeRef), _HasFile(nodeRef),
440 			entryContained, _CloserToPath(entry));
441 
442 		if (entry.IsDirectory()) {
443 			if (!_HasDirectory(nodeRef)
444 				&& (entryContained || _CloserToPath(entry))) {
445 				// there is a new directory to watch for us
446 				if (entryContained
447 					|| parentContained && !_WatchRecursively()) {
448 					_AddDirectory(entry);
449 					// NOTE: entry is toast now!
450 				} else if (_GetClosest(fPath.Path(), false,
451 						nodeRef) == B_OK) {
452 					// the new directory might put us even
453 					// closer to the path we are after
454 					_AddDirectory(nodeRef);
455 				}
456 
457 				wasAdded = true;
458 				notify = entryContained;
459 			}
460 			if (_WatchFilesOnly())
461 				notify = false;
462 		} else if (!_HasFile(nodeRef) && entryContained) {
463 			// file has been added
464 			wasAdded = true;
465 			notify = true;
466 			_AddFile(entry);
467 		}
468 	} else {
469 		// and entry has been removed from our directories
470 		wasRemoved = true;
471 
472 		nodeRef.node = node;
473 		if (entry.IsDirectory()) {
474 			if (_HasDirectory(nodeRef, &notify))
475 				_RemoveDirectory(entry, fromNode);
476 			if (_WatchFilesOnly())
477 				notify = false;
478 		} else {
479 			_RemoveFile(entry);
480 			notify = true;
481 		}
482 	}
483 
484 	if (notify) {
485 		if (wasAdded)
486 			message->AddBool("added", true);
487 		if (wasRemoved)
488 			message->AddBool("removed", true);
489 
490 		_NotifyTarget(message, nodeRef);
491 	}
492 }
493 
494 
495 void
496 PathHandler::MessageReceived(BMessage* message)
497 {
498 	switch (message->what) {
499 		case B_NODE_MONITOR:
500 		{
501 			int32 opcode;
502 			if (message->FindInt32("opcode", &opcode) != B_OK)
503 				return;
504 
505 			switch (opcode) {
506 				case B_ENTRY_CREATED:
507 					_EntryCreated(message);
508 					break;
509 
510 				case B_ENTRY_REMOVED:
511 					_EntryRemoved(message);
512 					break;
513 
514 				case B_ENTRY_MOVED:
515 					_EntryMoved(message);
516 					break;
517 
518 				default:
519 					_NotifyTarget(message);
520 					break;
521 			}
522 			break;
523 		}
524 
525 		default:
526 			BHandler::MessageReceived(message);
527 			break;
528 	}
529 
530 //#ifdef TRACE_PATH_MONITOR
531 //	Dump();
532 //#endif
533 }
534 
535 
536 bool
537 PathHandler::_IsContained(const node_ref& nodeRef) const
538 {
539 	BDirectory directory(&nodeRef);
540 	if (directory.InitCheck() != B_OK)
541 		return false;
542 
543 	BEntry entry;
544 	if (directory.GetEntry(&entry) != B_OK)
545 		return false;
546 
547 	return _IsContained(entry);
548 }
549 
550 
551 bool
552 PathHandler::_IsContained(BEntry& entry) const
553 {
554 	BPath path;
555 	if (entry.GetPath(&path) != B_OK)
556 		return false;
557 
558 	bool contained = strncmp(path.Path(), fPath.Path(), fPathLength) == 0;
559 	if (!contained)
560 		return false;
561 
562 	// Prevent the case that the entry is in another folder which happens
563 	// to have the same substring for fPathLength chars, like:
564 	// /path/we/are/watching
565 	// /path/we/are/watching-not/subfolder/entry
566 	// NOTE: We wouldn't be here if path.Path() was shorter than fPathLength,
567 	// strncmp() catches that case.
568 	const char* last = &path.Path()[fPathLength];
569 	if (last[0] && last[0] != '/')
570 		return false;
571 
572 	return true;
573 }
574 
575 
576 bool
577 PathHandler::_HasDirectory(const node_ref& nodeRef,
578 	bool* _contained /* = NULL */) const
579 {
580 	WatchedDirectory directory;
581 	directory.node = nodeRef;
582 
583 	DirectorySet::const_iterator iterator = fDirectories.find(directory);
584 	if (iterator == fDirectories.end())
585 		return false;
586 
587 	if (_contained != NULL)
588 		*_contained = iterator->contained;
589 	return true;
590 }
591 
592 
593 bool
594 PathHandler::_CloserToPath(BEntry& entry) const
595 {
596 	BPath path;
597 	if (entry.GetPath(&path) != B_OK)
598 		return false;
599 
600 	return strncmp(path.Path(), fPath.Path(), strlen(path.Path())) == 0;
601 }
602 
603 
604 void
605 PathHandler::_NotifyTarget(BMessage* message) const
606 {
607 	// NOTE: This version is only used for B_STAT_CHANGED and B_ATTR_CHANGED
608 	node_ref nodeRef;
609 	if (message->FindInt32("device", &nodeRef.device) != B_OK
610 		|| message->FindInt64("node", &nodeRef.node) != B_OK)
611 		return;
612 	_NotifyTarget(message, nodeRef);
613 }
614 
615 
616 void
617 PathHandler::_NotifyTarget(BMessage* message, const node_ref& nodeRef) const
618 {
619 	BMessage update(*message);
620 	update.what = B_PATH_MONITOR;
621 
622 	WatchedDirectory directory;
623 	directory.node = nodeRef;
624 
625 	DirectorySet::const_iterator iterator = fDirectories.find(directory);
626 	if (iterator != fDirectories.end()) {
627 		if (_WatchFilesOnly()) {
628 			// stat or attr notification for a directory
629 			return;
630 		}
631 		BDirectory nodeDirectory(&nodeRef);
632 		BEntry entry;
633 		if (nodeDirectory.GetEntry(&entry) == B_OK) {
634 			BPath path(&entry);
635 			update.AddString("path", path.Path());
636 		}
637 	} else {
638 		if (_WatchFoldersOnly()) {
639 			// this is bound to be a notification for a file
640 			return;
641 		}
642 		FileEntry setEntry;
643 		setEntry.ref.device = nodeRef.device;
644 		setEntry.node = nodeRef.node;
645 			// name does not need to be set, since it's not used for comparing
646 		FileSet::const_iterator i = fFiles.find(setEntry);
647 		if (i != fFiles.end()) {
648 			BPath path(&(i->ref));
649 			update.AddString("path", path.Path());
650 		}
651 	}
652 
653 	// This is in case the target is interested in figuring out which
654 	// BPathMonitor::StartWatching() call the message is resulting from.
655 	update.AddString("watched_path", fPath.Path());
656 
657 	fTarget.SendMessage(&update);
658 }
659 
660 
661 status_t
662 PathHandler::_AddDirectory(BEntry& entry)
663 {
664 	WatchedDirectory directory;
665 	status_t status = entry.GetNodeRef(&directory.node);
666 	if (status != B_OK)
667 		return status;
668 
669 #ifdef TRACE_PATH_MONITOR
670 {
671 	BPath path(&entry);
672 	printf("  ADD DIRECTORY %s, %ld:%Ld\n",
673 		path.Path(), directory.node.device, directory.node.node);
674 }
675 #endif
676 
677 	// check if we are already know this directory
678 
679 	// TODO: It should be possible to ommit this check if we know it
680 	// can't be the case (for example when adding subfolders recursively,
681 	// although in that case, the API user may still have added this folder
682 	// independently, so for now, it should be the safest to perform this
683 	// check in all cases.)
684 	if (_HasDirectory(directory.node))
685 		return B_OK;
686 
687 	directory.contained = _IsContained(entry);
688 
689 	uint32 flags;
690 	if (directory.contained)
691 		flags = (fFlags & WATCH_NODE_FLAG_MASK) | B_WATCH_DIRECTORY;
692 	else
693 		flags = B_WATCH_DIRECTORY;
694 
695 	status = watch_node(&directory.node, flags, this);
696 	if (status != B_OK)
697 		return status;
698 
699 	fDirectories.insert(directory);
700 
701 	if (_WatchRecursively()) {
702 		BDirectory dir(&directory.node);
703 		while (dir.GetNextEntry(&entry) == B_OK) {
704 			if (entry.IsDirectory()) {
705 				// and here is the recursion:
706 				if (_AddDirectory(entry) != B_OK)
707 					break;
708 			} else if (!_WatchFoldersOnly()) {
709 				if (_AddFile(entry) != B_OK)
710 					break;
711 			}
712 		}
713 	}
714 
715 #if 0
716 	BEntry parent;
717 	if (entry.GetParent(&parent) == B_OK
718 		&& !_IsContained(parent)) {
719 		// TODO: remove parent from watched directories
720 	}
721 #endif
722 	return B_OK;
723 }
724 
725 
726 status_t
727 PathHandler::_AddDirectory(node_ref& nodeRef)
728 {
729 	BDirectory directory(&nodeRef);
730 	status_t status = directory.InitCheck();
731 	if (status == B_OK) {
732 		BEntry entry;
733 		status = directory.GetEntry(&entry);
734 		if (status == B_OK)
735 			status = _AddDirectory(entry);
736 	}
737 
738 	return status;
739 }
740 
741 
742 status_t
743 PathHandler::_RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode)
744 {
745 	TRACE("  REMOVE DIRECTORY %ld:%Ld\n", nodeRef.device, nodeRef.node);
746 
747 	WatchedDirectory directory;
748 	directory.node = nodeRef;
749 
750 	DirectorySet::iterator iterator = fDirectories.find(directory);
751 	if (iterator == fDirectories.end())
752 		return B_ENTRY_NOT_FOUND;
753 
754 	watch_node(&directory.node, B_STOP_WATCHING, this);
755 
756 	node_ref directoryRef;
757 	directoryRef.device = nodeRef.device;
758 	directoryRef.node = directoryNode;
759 
760 	if (!_HasDirectory(directoryRef)) {
761 		// we don't have the parent directory now, but we'll need it in order
762 		// to find this directory again in case it's added again
763 		if (_AddDirectory(directoryRef) != B_OK
764 			&& _GetClosest(fPath.Path(), false, directoryRef) == B_OK)
765 			_AddDirectory(directoryRef);
766 	}
767 
768 	fDirectories.erase(iterator);
769 
770 	// TODO: stop watching subdirectories and their files when in recursive
771 	// mode!
772 
773 	return B_OK;
774 }
775 
776 
777 status_t
778 PathHandler::_RemoveDirectory(BEntry& entry, ino_t directoryNode)
779 {
780 	node_ref nodeRef;
781 	status_t status = entry.GetNodeRef(&nodeRef);
782 	if (status != B_OK)
783 		return status;
784 
785 	return _RemoveDirectory(nodeRef, directoryNode);
786 }
787 
788 
789 bool
790 PathHandler::_HasFile(const node_ref& nodeRef) const
791 {
792 	FileEntry setEntry;
793 	setEntry.ref.device = nodeRef.device;
794 	setEntry.node = nodeRef.node;
795 		// name does not need to be set, since it's not used for comparing
796 	FileSet::const_iterator iterator = fFiles.find(setEntry);
797 	return iterator != fFiles.end();
798 }
799 
800 
801 status_t
802 PathHandler::_AddFile(BEntry& entry)
803 {
804 	if ((fFlags & (WATCH_NODE_FLAG_MASK & ~B_WATCH_DIRECTORY)) == 0)
805 		return B_OK;
806 
807 #ifdef TRACE_PATH_MONITOR
808 {
809 	BPath path(&entry);
810 	printf("  ADD FILE %s\n", path.Path());
811 }
812 #endif
813 
814 	node_ref nodeRef;
815 	status_t status = entry.GetNodeRef(&nodeRef);
816 	if (status != B_OK)
817 		return status;
818 
819 	// check if we are already know this file
820 
821 	// TODO: It should be possible to ommit this check if we know it
822 	// can't be the case (for example when adding subfolders recursively,
823 	// although in that case, the API user may still have added this file
824 	// independently, so for now, it should be the safest to perform this
825 	// check in all cases.)
826 	if (_HasFile(nodeRef))
827 		return B_OK;
828 
829 	status = watch_node(&nodeRef, (fFlags & WATCH_NODE_FLAG_MASK), this);
830 	if (status != B_OK)
831 		return status;
832 
833 	FileEntry setEntry;
834 	entry.GetRef(&setEntry.ref);
835 	setEntry.node = nodeRef.node;
836 
837 	fFiles.insert(setEntry);
838 	return B_OK;
839 }
840 
841 
842 status_t
843 PathHandler::_RemoveFile(const node_ref& nodeRef)
844 {
845 	TRACE("  REMOVE FILE %ld:%Ld\n", nodeRef.device, nodeRef.node);
846 
847 	FileEntry setEntry;
848 	setEntry.ref.device = nodeRef.device;
849 	setEntry.node = nodeRef.node;
850 		// name does not need to be set, since it's not used for comparing
851 	FileSet::iterator iterator = fFiles.find(setEntry);
852 	if (iterator == fFiles.end())
853 		return B_ENTRY_NOT_FOUND;
854 
855 	watch_node(&nodeRef, B_STOP_WATCHING, this);
856 	fFiles.erase(iterator);
857 	return B_OK;
858 }
859 
860 
861 status_t
862 PathHandler::_RemoveFile(BEntry& entry)
863 {
864 	node_ref nodeRef;
865 	status_t status = entry.GetNodeRef(&nodeRef);
866 	if (status != B_OK)
867 		return status;
868 
869 	return _RemoveFile(nodeRef);
870 }
871 
872 
873 //	#pragma mark -
874 
875 
876 BPathMonitor::BPathMonitor()
877 {
878 }
879 
880 
881 BPathMonitor::~BPathMonitor()
882 {
883 }
884 
885 
886 /*static*/ status_t
887 BPathMonitor::_InitLockerIfNeeded()
888 {
889 	static vint32 lock = 0;
890 
891 	if (sLocker != NULL)
892 		return B_OK;
893 
894 	while (sLocker == NULL) {
895 		if (atomic_add(&lock, 1) == 0) {
896 			sLocker = new (nothrow) BLocker("path monitor");
897 			if (sLocker == NULL)
898 				return B_NO_MEMORY;
899 		}
900 		snooze(5000);
901 	}
902 
903 	return B_OK;
904 }
905 
906 
907 /*static*/ status_t
908 BPathMonitor::_InitLooperIfNeeded()
909 {
910 	static vint32 lock = 0;
911 
912 	if (sLooper != NULL)
913 		return B_OK;
914 
915 	while (sLooper == NULL) {
916 		if (atomic_add(&lock, 1) == 0) {
917 			// first thread initializes the global looper
918 			sLooper = new (nothrow) BLooper("PathMonitor looper");
919 			if (sLooper == NULL)
920 				return B_NO_MEMORY;
921 			thread_id thread = sLooper->Run();
922 			if (thread < B_OK)
923 				return (status_t)thread;
924 		}
925 		snooze(5000);
926 	}
927 
928 	return sLooper->Thread() >= 0 ? B_OK : B_ERROR;
929 }
930 
931 
932 /*static*/ status_t
933 BPathMonitor::StartWatching(const char* path, uint32 flags, BMessenger target)
934 {
935 	status_t status = _InitLockerIfNeeded();
936 	if (status != B_OK)
937 		return status;
938 
939 	// use the global looper for receiving node monitor notifications
940 	status = _InitLooperIfNeeded();
941 	if (status < B_OK)
942 		return status;
943 
944 	BAutolock _(sLocker);
945 
946 	WatcherMap::iterator iterator = sWatchers.find(target);
947 	Watcher* watcher = NULL;
948 	if (iterator != sWatchers.end())
949 		watcher = iterator->second;
950 
951 	PathHandler* handler = new (nothrow) PathHandler(path, flags, target,
952 		sLooper);
953 	if (handler == NULL)
954 		return B_NO_MEMORY;
955 	status = handler->InitCheck();
956 	if (status < B_OK)
957 		return status;
958 
959 	if (watcher == NULL) {
960 		watcher = new (nothrow) BPrivate::Watcher;
961 		if (watcher == NULL)
962 			return B_NO_MEMORY;
963 		sWatchers[target] = watcher;
964 	}
965 
966 	watcher->handlers[path] = handler;
967 	return B_OK;
968 }
969 
970 
971 /*static*/ status_t
972 BPathMonitor::StopWatching(const char* path, BMessenger target)
973 {
974 	if (sLocker == NULL)
975 		return B_NO_INIT;
976 
977 	BAutolock _(sLocker);
978 
979 	WatcherMap::iterator iterator = sWatchers.find(target);
980 	if (iterator == sWatchers.end())
981 		return B_BAD_VALUE;
982 
983 	Watcher* watcher = iterator->second;
984 	HandlerMap::iterator i = watcher->handlers.find(path);
985 
986 	if (i == watcher->handlers.end())
987 		return B_BAD_VALUE;
988 
989 	PathHandler* handler = i->second;
990 	watcher->handlers.erase(i);
991 
992 	handler->Quit();
993 
994 	if (watcher->handlers.empty()) {
995 		sWatchers.erase(iterator);
996 		delete watcher;
997 	}
998 
999 	return B_OK;
1000 }
1001 
1002 
1003 /*static*/ status_t
1004 BPathMonitor::StopWatching(BMessenger target)
1005 {
1006 	if (sLocker == NULL)
1007 		return B_NO_INIT;
1008 
1009 	BAutolock _(sLocker);
1010 
1011 	WatcherMap::iterator iterator = sWatchers.find(target);
1012 	if (iterator == sWatchers.end())
1013 		return B_BAD_VALUE;
1014 
1015 	Watcher* watcher = iterator->second;
1016 	while (!watcher->handlers.empty()) {
1017 		HandlerMap::iterator i = watcher->handlers.begin();
1018 		PathHandler* handler = i->second;
1019 		watcher->handlers.erase(i);
1020 
1021 		handler->Quit();
1022 	}
1023 
1024 	sWatchers.erase(iterator);
1025 	delete watcher;
1026 
1027 	return B_OK;
1028 }
1029 
1030 }	// namespace BPrivate
1031