xref: /haiku/src/kits/storage/PathMonitor.cpp (revision 14e3d1b5768e7110b3d5c0855833267409b71dbb)
1 /*
2  * Copyright 2007, 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  */
8 
9 
10 #include <PathMonitor.h>
11 
12 #include <ObjectList.h>
13 
14 #include <Application.h>
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 <set>
26 
27 using namespace BPrivate;
28 using namespace std;
29 
30 
31 #define WATCH_NODE_FLAG_MASK	0x00ff
32 
33 namespace BPrivate {
34 
35 struct watched_directory {
36 	node_ref	node;
37 	bool		contained;
38 };
39 typedef set<watched_directory> DirectorySet;
40 typedef set<node_ref> FileSet;
41 
42 class PathHandler;
43 typedef map<BString, PathHandler*> HandlerMap;
44 
45 struct watcher {
46 	HandlerMap handlers;
47 };
48 typedef map<BMessenger, watcher*> WatcherMap;
49 
50 class PathHandler : public BHandler {
51 	public:
52 		PathHandler(const char* path, uint32 flags, BMessenger target);
53 		virtual ~PathHandler();
54 
55 		status_t InitCheck() const;
56 		void SetTarget(BMessenger target);
57 		void Quit();
58 
59 		virtual void MessageReceived(BMessage* message);
60 		void Dump();
61 
62 	private:
63 		status_t _GetClosest(const char* path, bool updatePath,
64 			node_ref& nodeRef);
65 		bool _WatchRecursively();
66 		bool _WatchFilesOnly();
67 		void _EntryCreated(BMessage* message);
68 		void _EntryRemoved(BMessage* message);
69 		void _EntryMoved(BMessage* message);
70 		bool _IsContained(const node_ref& nodeRef) const;
71 		bool _IsContained(BEntry& entry) const;
72 		bool _HasDirectory(const node_ref& nodeRef, bool* _contained = NULL) const;
73 		bool _CloserToPath(BEntry& entry) const;
74 		void _NotifyTarget(BMessage* message) const;
75 		status_t _AddDirectory(BEntry& entry);
76 		status_t _AddDirectory(node_ref& nodeRef);
77 		status_t _RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode);
78 		status_t _RemoveDirectory(BEntry& entry, ino_t directoryNode);
79 
80 		bool _HasFile(const node_ref& nodeRef) const;
81 		status_t _AddFile(BEntry& entry);
82 		status_t _RemoveFile(const node_ref& nodeRef);
83 		status_t _RemoveFile(BEntry& entry);
84 
85 		BPath			fPath;
86 		int32			fPathLength;
87 		BMessenger		fTarget;
88 		uint32			fFlags;
89 		status_t		fStatus;
90 		bool			fOwnsLooper;
91 		DirectorySet	fDirectories;
92 		FileSet			fFiles;
93 };
94 
95 
96 static WatcherMap sWatchers;
97 static BLocker* sLocker;
98 
99 
100 static status_t
101 set_entry(node_ref& nodeRef, const char* name, BEntry& entry)
102 {
103 	entry_ref ref;
104 	ref.device = nodeRef.device;
105 	ref.directory = nodeRef.node;
106 
107 	status_t status = ref.set_name(name);
108 	if (status != B_OK)
109 		return status;
110 
111 	return entry.SetTo(&ref, true);
112 }
113 
114 
115 bool
116 operator<(const node_ref& a, const node_ref& b)
117 {
118 	if (a.device < b.device)
119 		return true;
120 	if (a.device == b.device && a.node < b.node)
121 		return true;
122 
123 	return false;
124 }
125 
126 
127 bool
128 operator<(const watched_directory& a, const watched_directory& b)
129 {
130 	return a.node < b.node;
131 }
132 
133 
134 //	#pragma mark -
135 
136 
137 PathHandler::PathHandler(const char* path, uint32 flags, BMessenger target)
138 	: BHandler(path),
139 	fTarget(target),
140 	fFlags(flags),
141 	fOwnsLooper(false)
142 {
143 	if (path == NULL || !path[0]) {
144 		fStatus = B_BAD_VALUE;
145 		return;
146 	}
147 
148 	// TODO: support watching not-yet-mounted volumes as well!
149 	node_ref nodeRef;
150 	fStatus = _GetClosest(path, true, nodeRef);
151 	if (fStatus < B_OK)
152 		return;
153 
154 	BLooper* looper;
155 	if (be_app != NULL) {
156 		looper = be_app;
157 	} else {
158 		// TODO: only have a single global looper!
159 		looper = new BLooper("PathMonitor looper");
160 		looper->Run();
161 		fOwnsLooper = true;
162 	}
163 
164 	looper->Lock();
165 	looper->AddHandler(this);
166 	looper->Unlock();
167 
168 	fStatus = _AddDirectory(nodeRef);
169 }
170 
171 
172 PathHandler::~PathHandler()
173 {
174 }
175 
176 
177 status_t
178 PathHandler::InitCheck() const
179 {
180 	return fStatus;
181 }
182 
183 
184 void
185 PathHandler::Quit()
186 {
187 	BMessenger me(this);
188 	me.SendMessage(B_QUIT_REQUESTED);
189 }
190 
191 
192 void
193 PathHandler::Dump()
194 {
195 	printf("WATCHING DIRECTORIES:\n");
196 	DirectorySet::iterator i = fDirectories.begin();
197 	for (; i != fDirectories.end(); i++) {
198 		printf("  %ld:%Ld (%s)\n", i->node.device, i->node.node, i->contained ? "contained" : "-");
199 	}
200 
201 	printf("WATCHING FILES:\n");
202 
203 	FileSet::iterator j = fFiles.begin();
204 	for (; j != fFiles.end(); j++) {
205 		printf("  %ld:%Ld\n", j->device, j->node);
206 	}
207 }
208 
209 
210 status_t
211 PathHandler::_GetClosest(const char* path, bool updatePath, node_ref& nodeRef)
212 {
213 	BPath first(path);
214 	BString missing;
215 
216 	while (true) {
217 		// try to find the first part of the path that exists
218 		BDirectory directory;
219 		status_t status = directory.SetTo(first.Path());
220 		if (status == B_OK) {
221 			status = directory.GetNodeRef(&nodeRef);
222 			if (status == B_OK) {
223 				if (updatePath) {
224 					// normalize path
225 					status = fPath.SetTo(&directory, NULL, true);
226 					if (status == B_OK) {
227 						fPath.Append(missing.String());
228 						fPathLength = strlen(fPath.Path());
229 					}
230 				}
231 				return status;
232 			}
233 		}
234 
235 		if (updatePath) {
236 			if (missing.Length() > 0)
237 				missing.Prepend("/");
238 			missing.Prepend(first.Leaf());
239 		}
240 
241 		if (first.GetParent(&first) != B_OK)
242 			return B_ERROR;
243 	}
244 }
245 
246 
247 bool
248 PathHandler::_WatchRecursively()
249 {
250 	return (fFlags & B_WATCH_RECURSIVELY) != 0;
251 }
252 
253 
254 bool
255 PathHandler::_WatchFilesOnly()
256 {
257 	return (fFlags & B_WATCH_FILES_ONLY) != 0;
258 }
259 
260 
261 void
262 PathHandler::_EntryCreated(BMessage* message)
263 {
264 	const char* name;
265 	node_ref nodeRef;
266 	if (message->FindInt32("device", &nodeRef.device) != B_OK
267 		|| message->FindInt64("directory", &nodeRef.node) != B_OK
268 		|| message->FindString("name", &name) != B_OK)
269 		return;
270 
271 	BEntry entry;
272 	if (set_entry(nodeRef, name, entry) != B_OK)
273 		return;
274 
275 	bool parentContained = false;
276 	bool entryContained = _IsContained(entry);
277 	if (entryContained)
278 		parentContained = _IsContained(nodeRef);
279 	bool notify = entryContained;
280 
281 	if (entry.IsDirectory()) {
282 		// ignore the directory if it's already known
283 		if (entry.GetNodeRef(&nodeRef) == B_OK
284 			&& _HasDirectory(nodeRef)) {
285 			printf("    WE ALREADY HAVE DIR %s, %ld:%Ld\n",
286 				name, nodeRef.device, nodeRef.node);
287 			return;
288 		}
289 
290 		// a new directory to watch for us
291 		if (!entryContained && !_CloserToPath(entry)
292 			|| parentContained && !_WatchRecursively()
293 			|| _AddDirectory(entry) != B_OK
294 			|| _WatchFilesOnly())
295 			notify = parentContained;
296 	} else if (entryContained) {
297 		printf("  NEW ENTRY PARENT CONTAINED: %d\n", parentContained);
298 		_AddFile(entry);
299 	}
300 
301 	if (notify && entryContained) {
302 		message->AddBool("added", true);
303 		_NotifyTarget(message);
304 	}
305 }
306 
307 
308 void
309 PathHandler::_EntryRemoved(BMessage* message)
310 {
311 	node_ref nodeRef;
312 	uint64 directoryNode;
313 	if (message->FindInt32("device", &nodeRef.device) != B_OK
314 		|| message->FindInt64("directory", (int64 *)&directoryNode) != B_OK
315 		|| message->FindInt64("node", &nodeRef.node) != B_OK)
316 		return;
317 
318 	bool notify = false;
319 
320 	if (_HasDirectory(nodeRef, &notify)) {
321 		// the directory has been removed, so we remove it as well
322 		_RemoveDirectory(nodeRef, directoryNode);
323 		if (_WatchFilesOnly())
324 			notify = false;
325 	} else if (_HasFile(nodeRef)) {
326 		_RemoveFile(nodeRef);
327 		notify = true;
328 	}
329 
330 	if (notify) {
331 		message->AddBool("removed", true);
332 		_NotifyTarget(message);
333 	}
334 }
335 
336 
337 void
338 PathHandler::_EntryMoved(BMessage* message)
339 {
340 	// has the entry been moved into a monitored directory or has
341 	// it been removed from one?
342 	const char* name;
343 	node_ref nodeRef;
344 	uint64 fromNode;
345 	uint64 node;
346 	if (message->FindInt32("device", &nodeRef.device) != B_OK
347 		|| message->FindInt64("to directory", &nodeRef.node) != B_OK
348 		|| message->FindInt64("from directory", (int64 *)&fromNode) != B_OK
349 		|| message->FindInt64("node", (int64 *)&node) != B_OK
350 		|| message->FindString("name", &name) != B_OK)
351 		return;
352 
353 	BEntry entry;
354 	if (set_entry(nodeRef, name, entry) != B_OK)
355 		return;
356 
357 	bool entryContained = _IsContained(entry);
358 	bool wasAdded = false;
359 	bool wasRemoved = false;
360 	bool notify = false;
361 
362 	bool parentContained;
363 	if (_HasDirectory(nodeRef, &parentContained)) {
364 		// something has been added to our watched directories
365 
366 		nodeRef.node = node;
367 printf("    ADDED TO PARENT (%d), has entry %d/%d, entry %d %d\n",
368 parentContained, _HasDirectory(nodeRef), _HasFile(nodeRef),
369 entryContained, _CloserToPath(entry));
370 
371 		if (entry.IsDirectory()) {
372 			if (!_HasDirectory(nodeRef)
373 				&& (entryContained || _CloserToPath(entry))) {
374 				// there is a new directory to watch for us
375 				if (entryContained
376 					|| parentContained && !_WatchRecursively())
377 					_AddDirectory(entry);
378 				else if (_GetClosest(fPath.Path(), false,
379 						nodeRef) == B_OK) {
380 					// the new directory might put us even
381 					// closer to the path we are after
382 					_AddDirectory(nodeRef);
383 				}
384 
385 				wasAdded = true;
386 				notify = entryContained;
387 			}
388 			if (_WatchFilesOnly())
389 				notify = false;
390 		} else if (!_HasFile(nodeRef) && entryContained) {
391 			// file has been added
392 			wasAdded = true;
393 			notify = true;
394 			_AddFile(entry);
395 		}
396 	} else {
397 		// and entry has been removed from our directories
398 		wasRemoved = true;
399 
400 		nodeRef.node = node;
401 		if (entry.IsDirectory()) {
402 			if (_HasDirectory(nodeRef, &notify))
403 				_RemoveDirectory(entry, fromNode);
404 			if (_WatchFilesOnly())
405 				notify = false;
406 		} else {
407 			_RemoveFile(entry);
408 			notify = true;
409 		}
410 	}
411 
412 	if (notify) {
413 		if (wasAdded)
414 			message->AddBool("added", true);
415 		if (wasRemoved)
416 			message->AddBool("removed", true);
417 
418 		_NotifyTarget(message);
419 	}
420 }
421 
422 
423 void
424 PathHandler::MessageReceived(BMessage* message)
425 {
426 	switch (message->what) {
427 		case B_NODE_MONITOR:
428 		{
429 			int32 opcode;
430 			if (message->FindInt32("opcode", &opcode) != B_OK)
431 				return;
432 
433 			switch (opcode) {
434 				case B_ENTRY_CREATED:
435 					_EntryCreated(message);
436 					break;
437 
438 				case B_ENTRY_REMOVED:
439 					_EntryRemoved(message);
440 					break;
441 
442 				case B_ENTRY_MOVED:
443 					_EntryMoved(message);
444 					break;
445 
446 				default:
447 					_NotifyTarget(message);
448 					break;
449 			}
450 			break;
451 		}
452 
453 		case B_QUIT_REQUESTED:
454 		{
455 			BLooper* looper = Looper();
456 
457 			stop_watching(this);
458 			looper->RemoveHandler(this);
459 			delete this;
460 
461 			if (fOwnsLooper)
462 				looper->Quit();
463 			return;
464 		}
465 
466 		default:
467 			BHandler::MessageReceived(message);
468 			break;
469 	}
470 
471 	Dump();
472 }
473 
474 
475 bool
476 PathHandler::_IsContained(const node_ref& nodeRef) const
477 {
478 	BDirectory directory(&nodeRef);
479 	if (directory.InitCheck() != B_OK)
480 		return false;
481 
482 	BEntry entry;
483 	if (directory.GetEntry(&entry) != B_OK)
484 		return false;
485 
486 	return _IsContained(entry);
487 }
488 
489 
490 bool
491 PathHandler::_IsContained(BEntry& entry) const
492 {
493 	BPath path;
494 	if (entry.GetPath(&path) != B_OK)
495 		return false;
496 
497 	bool contained = strncmp(path.Path(), fPath.Path(), fPathLength) == 0;
498 	if (!contained)
499 		return false;
500 
501 	const char* last = &path.Path()[fPathLength];
502 	if (last[0] && last[0] != '/')
503 		return false;
504 
505 	return true;
506 }
507 
508 
509 bool
510 PathHandler::_HasDirectory(const node_ref& nodeRef, bool* _contained = NULL) const
511 {
512 	watched_directory directory;
513 	directory.node = nodeRef;
514 
515 	DirectorySet::const_iterator iterator = fDirectories.find(directory);
516 	if (iterator == fDirectories.end())
517 		return false;
518 
519 	if (_contained != NULL)
520 		*_contained = iterator->contained;
521 	return true;
522 }
523 
524 
525 bool
526 PathHandler::_CloserToPath(BEntry& entry) const
527 {
528 	BPath path;
529 	if (entry.GetPath(&path) != B_OK)
530 		return false;
531 
532 	return strncmp(path.Path(), fPath.Path(), strlen(path.Path())) == 0;
533 }
534 
535 
536 void
537 PathHandler::_NotifyTarget(BMessage* message) const
538 {
539 	BMessage update(*message);
540 	update.what = B_PATH_MONITOR;
541 	fTarget.SendMessage(&update);
542 }
543 
544 
545 status_t
546 PathHandler::_AddDirectory(BEntry& entry)
547 {
548 	watched_directory directory;
549 	status_t status = entry.GetNodeRef(&directory.node);
550 	if (status != B_OK)
551 		return status;
552 
553 //#ifdef TRACE_MONITOR
554 {
555 	BPath path(&entry);
556 	printf("  ADD DIRECTORY %s, %ld:%Ld\n",
557 		path.Path(), directory.node.device, directory.node.node);
558 }
559 //#endif
560 
561 	// check if we are already know this directory
562 
563 	if (_HasDirectory(directory.node))
564 		return B_OK;
565 
566 	directory.contained = _IsContained(entry);
567 
568 	uint32 flags;
569 	if (directory.contained)
570 		flags = (fFlags & WATCH_NODE_FLAG_MASK) | B_WATCH_DIRECTORY;
571 	else
572 		flags = B_WATCH_DIRECTORY;
573 
574 	status = watch_node(&directory.node, flags, this);
575 	if (status != B_OK)
576 		return status;
577 
578 	fDirectories.insert(directory);
579 
580 #if 0
581 	BEntry parent;
582 	if (entry.GetParent(&parent) == B_OK
583 		&& !_IsContained(parent)) {
584 		// TODO: remove parent from watched directories
585 	}
586 #endif
587 	return B_OK;
588 }
589 
590 
591 status_t
592 PathHandler::_AddDirectory(node_ref& nodeRef)
593 {
594 	BDirectory directory(&nodeRef);
595 	status_t status = directory.InitCheck();
596 	if (status == B_OK) {
597 		BEntry entry;
598 		status = directory.GetEntry(&entry);
599 		if (status == B_OK)
600 			status = _AddDirectory(entry);
601 	}
602 
603 	return status;
604 }
605 
606 
607 status_t
608 PathHandler::_RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode)
609 {
610 	printf("  REMOVE DIRECTORY %ld:%Ld\n", nodeRef.device, nodeRef.node);
611 
612 	watched_directory directory;
613 	directory.node = nodeRef;
614 
615 	DirectorySet::iterator iterator = fDirectories.find(directory);
616 	if (iterator == fDirectories.end())
617 		return B_ENTRY_NOT_FOUND;
618 
619 	watch_node(&directory.node, B_STOP_WATCHING, this);
620 
621 	node_ref directoryRef;
622 	directoryRef.device = nodeRef.device;
623 	directoryRef.node = directoryNode;
624 
625 	if (!_HasDirectory(directoryRef)) {
626 		// we don't have the parent directory now, but we'll need it in order
627 		// to find this directory again in case it's added again
628 		if (_AddDirectory(directoryRef) != B_OK
629 			&& _GetClosest(fPath.Path(), false, directoryRef) == B_OK)
630 			_AddDirectory(directoryRef);
631 	}
632 
633 	fDirectories.erase(iterator);
634 	return B_OK;
635 }
636 
637 
638 status_t
639 PathHandler::_RemoveDirectory(BEntry& entry, ino_t directoryNode)
640 {
641 	node_ref nodeRef;
642 	status_t status = entry.GetNodeRef(&nodeRef);
643 	if (status != B_OK)
644 		return status;
645 
646 	return _RemoveDirectory(nodeRef, directoryNode);
647 }
648 
649 
650 bool
651 PathHandler::_HasFile(const node_ref& nodeRef) const
652 {
653 	FileSet::const_iterator iterator = fFiles.find(nodeRef);
654 	return iterator != fFiles.end();
655 }
656 
657 
658 status_t
659 PathHandler::_AddFile(BEntry& entry)
660 {
661 	if ((fFlags & (WATCH_NODE_FLAG_MASK & ~B_WATCH_DIRECTORY)) == 0)
662 		return B_OK;
663 
664 {
665 	BPath path(&entry);
666 	printf("  ADD FILE %s\n", path.Path());
667 }
668 
669 	node_ref nodeRef;
670 	status_t status = entry.GetNodeRef(&nodeRef);
671 	if (status != B_OK)
672 		return status;
673 
674 	// check if we are already know this file
675 
676 	if (_HasFile(nodeRef))
677 		return B_OK;
678 
679 	status = watch_node(&nodeRef, (fFlags & WATCH_NODE_FLAG_MASK), this);
680 	if (status != B_OK)
681 		return status;
682 
683 	fFiles.insert(nodeRef);
684 	return B_OK;
685 }
686 
687 
688 status_t
689 PathHandler::_RemoveFile(const node_ref& nodeRef)
690 {
691 	printf("  REMOVE FILE %ld:%Ld\n", nodeRef.device, nodeRef.node);
692 
693 	FileSet::iterator iterator = fFiles.find(nodeRef);
694 	if (iterator == fFiles.end())
695 		return B_ENTRY_NOT_FOUND;
696 
697 	watch_node(&nodeRef, B_STOP_WATCHING, this);
698 	fFiles.erase(iterator);
699 	return B_OK;
700 }
701 
702 
703 status_t
704 PathHandler::_RemoveFile(BEntry& entry)
705 {
706 	node_ref nodeRef;
707 	status_t status = entry.GetNodeRef(&nodeRef);
708 	if (status != B_OK)
709 		return status;
710 
711 	return _RemoveFile(nodeRef);
712 }
713 
714 
715 //	#pragma mark -
716 
717 
718 BPathMonitor::BPathMonitor()
719 {
720 }
721 
722 
723 BPathMonitor::~BPathMonitor()
724 {
725 }
726 
727 
728 /*static*/ status_t
729 BPathMonitor::_InitIfNeeded()
730 {
731 	static vint32 lock = 0;
732 
733 	if (sLocker != NULL)
734 		return B_OK;
735 
736 	while (sLocker == NULL) {
737 		if (atomic_add(&lock, 1) == 0) {
738 			sLocker = new BLocker("path monitor");
739 		}
740 		snooze(5000);
741 	}
742 
743 	return B_OK;
744 }
745 
746 
747 /*static*/ status_t
748 BPathMonitor::StartWatching(const char* path, uint32 flags, BMessenger target)
749 {
750 	status_t status = _InitIfNeeded();
751 	if (status != B_OK)
752 		return status;
753 
754 	BAutolock _(sLocker);
755 
756 	WatcherMap::iterator iterator = sWatchers.find(target);
757 	struct watcher* watcher = NULL;
758 	if (iterator != sWatchers.end())
759 		watcher = iterator->second;
760 
761 	PathHandler* handler = new PathHandler(path, flags, target);
762 	status = handler->InitCheck();
763 	if (status < B_OK)
764 		return status;
765 
766 	if (watcher == NULL) {
767 		watcher = new BPrivate::watcher;
768 		sWatchers[target] = watcher;
769 	}
770 
771 	watcher->handlers[path] = handler;
772 	return B_OK;
773 }
774 
775 
776 /*static*/ status_t
777 BPathMonitor::StopWatching(const char* path, BMessenger target)
778 {
779 	BAutolock _(sLocker);
780 
781 	WatcherMap::iterator iterator = sWatchers.find(target);
782 	if (iterator == sWatchers.end())
783 		return B_BAD_VALUE;
784 
785 	struct watcher* watcher = iterator->second;
786 	HandlerMap::iterator i = watcher->handlers.find(path);
787 
788 	if (i == watcher->handlers.end())
789 		return B_BAD_VALUE;
790 
791 	PathHandler* handler = i->second;
792 	watcher->handlers.erase(i);
793 
794 	if (handler->LockLooper())
795 		handler->Quit();
796 
797 	if (watcher->handlers.empty()) {
798 		sWatchers.erase(iterator);
799 		delete watcher;
800 	}
801 
802 	return B_OK;
803 }
804 
805 
806 /*static*/ status_t
807 BPathMonitor::StopWatching(BMessenger target)
808 {
809 	BAutolock _(sLocker);
810 
811 	WatcherMap::iterator iterator = sWatchers.find(target);
812 	if (iterator == sWatchers.end())
813 		return B_BAD_VALUE;
814 
815 	struct watcher* watcher = iterator->second;
816 	HandlerMap::iterator i = watcher->handlers.begin();
817 	for (; i != watcher->handlers.end(); i++) {
818 		PathHandler* handler = i->second;
819 		watcher->handlers.erase(i);
820 
821 		if (handler->LockLooper())
822 			handler->Quit();
823 	}
824 
825 	sWatchers.erase(iterator);
826 	delete watcher;
827 
828 	return B_OK;
829 }
830 
831 }	// namespace BPrivate
832