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