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