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