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