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
37using namespace BPrivate;
38using namespace std;
39using 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
46namespace BPrivate {
47
48struct 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
68struct WatchedDirectory {
69	node_ref		node;
70	bool			contained;
71};
72typedef set<WatchedDirectory> DirectorySet;
73
74
75class PathHandler;
76typedef map<BString, PathHandler*> HandlerMap;
77
78struct Watcher {
79	HandlerMap handlers;
80};
81typedef map<BMessenger, Watcher*> WatcherMap;
82
83class 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
141static WatcherMap sWatchers;
142static BLocker* sLocker = NULL;
143static BLooper* sLooper = NULL;
144
145
146static status_t
147set_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
161bool
162operator<(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
173bool
174operator<(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
185bool
186operator<(const WatchedDirectory& a, const WatchedDirectory& b)
187{
188	return a.node < b.node;
189}
190
191
192//	#pragma mark -
193
194
195PathHandler::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
229PathHandler::~PathHandler()
230{
231}
232
233
234status_t
235PathHandler::InitCheck() const
236{
237	return fStatus;
238}
239
240
241void
242PathHandler::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
254void
255PathHandler::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
274status_t
275PathHandler::_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
311bool
312PathHandler::_WatchRecursively() const
313{
314	return (fFlags & B_WATCH_RECURSIVELY) != 0;
315}
316
317
318bool
319PathHandler::_WatchFilesOnly() const
320{
321	return (fFlags & B_WATCH_FILES_ONLY) != 0;
322}
323
324
325bool
326PathHandler::_WatchFoldersOnly() const
327{
328	return (fFlags & B_WATCH_FOLDERS_ONLY) != 0;
329}
330
331
332void
333PathHandler::_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
388void
389PathHandler::_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
414void
415PathHandler::_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
501void
502PathHandler::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
542bool
543PathHandler::_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
557bool
558PathHandler::_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
582bool
583PathHandler::_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
599bool
600PathHandler::_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
610void
611PathHandler::_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
622void
623PathHandler::_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
669status_t
670PathHandler::_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
734status_t
735PathHandler::_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
750status_t
751PathHandler::_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
797status_t
798PathHandler::_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
809bool
810PathHandler::_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
821status_t
822PathHandler::_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
877status_t
878PathHandler::_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
896status_t
897PathHandler::_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
908void
909PathHandler::_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
962BPathMonitor::BPathMonitor()
963{
964}
965
966
967BPathMonitor::~BPathMonitor()
968{
969}
970
971
972/*static*/ status_t
973BPathMonitor::_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
995BPathMonitor::_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
1021BPathMonitor::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
1066BPathMonitor::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
1100BPathMonitor::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