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