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