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, ¬ify)) { 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, ¬ify)) 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