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 looper->Lock(); 215 looper->AddHandler(this); 216 looper->Unlock(); 217 218 fStatus = _AddDirectory(nodeRef); 219 220 // TODO: work-around for existing files (should not watch the directory in 221 // this case) 222 BEntry entry(path); 223 if (entry.Exists() && !entry.IsDirectory()) 224 _AddFile(entry); 225 } 226 227 228 PathHandler::~PathHandler() 229 { 230 } 231 232 233 status_t 234 PathHandler::InitCheck() const 235 { 236 return fStatus; 237 } 238 239 240 void 241 PathHandler::Quit() 242 { 243 if (sLooper->Lock()) { 244 stop_watching(this); 245 sLooper->RemoveHandler(this); 246 sLooper->Unlock(); 247 } 248 delete this; 249 } 250 251 252 #ifdef TRACE_PATH_MONITOR 253 void 254 PathHandler::Dump() 255 { 256 TRACE("WATCHING DIRECTORIES:\n"); 257 DirectorySet::iterator i = fDirectories.begin(); 258 for (; i != fDirectories.end(); i++) { 259 TRACE(" %ld:%Ld (%s)\n", i->node.device, i->node.node, i->contained 260 ? "contained" : "-"); 261 } 262 263 TRACE("WATCHING FILES:\n"); 264 265 FileSet::iterator j = fFiles.begin(); 266 for (; j != fFiles.end(); j++) { 267 TRACE(" %ld:%Ld\n", j->ref.device, j->node); 268 } 269 } 270 #endif 271 272 273 status_t 274 PathHandler::_GetClosest(const char* path, bool updatePath, node_ref& nodeRef) 275 { 276 BPath first(path); 277 BString missing; 278 279 while (true) { 280 // try to find the first part of the path that exists 281 BDirectory directory; 282 status_t status = directory.SetTo(first.Path()); 283 if (status == B_OK) { 284 status = directory.GetNodeRef(&nodeRef); 285 if (status == B_OK) { 286 if (updatePath) { 287 // normalize path 288 status = fPath.SetTo(&directory, NULL, true); 289 if (status == B_OK) { 290 fPath.Append(missing.String()); 291 fPathLength = strlen(fPath.Path()); 292 } 293 } 294 return status; 295 } 296 } 297 298 if (updatePath) { 299 if (missing.Length() > 0) 300 missing.Prepend("/"); 301 missing.Prepend(first.Leaf()); 302 } 303 304 if (first.GetParent(&first) != B_OK) 305 return B_ERROR; 306 } 307 } 308 309 310 bool 311 PathHandler::_WatchRecursively() const 312 { 313 return (fFlags & B_WATCH_RECURSIVELY) != 0; 314 } 315 316 317 bool 318 PathHandler::_WatchFilesOnly() const 319 { 320 return (fFlags & B_WATCH_FILES_ONLY) != 0; 321 } 322 323 324 bool 325 PathHandler::_WatchFoldersOnly() const 326 { 327 return (fFlags & B_WATCH_FOLDERS_ONLY) != 0; 328 } 329 330 331 void 332 PathHandler::_EntryCreated(BMessage* message) 333 { 334 const char* name; 335 node_ref nodeRef; 336 if (message->FindInt32("device", &nodeRef.device) != B_OK 337 || message->FindInt64("directory", &nodeRef.node) != B_OK 338 || message->FindString("name", &name) != B_OK) { 339 TRACE("PathHandler::_EntryCreated() - malformed message!\n"); 340 return; 341 } 342 343 BEntry entry; 344 if (set_entry(nodeRef, name, entry) != B_OK) { 345 TRACE("PathHandler::_EntryCreated() - set_entry failed!\n"); 346 return; 347 } 348 349 bool parentContained = false; 350 bool entryContained = _IsContained(entry); 351 if (entryContained) 352 parentContained = _IsContained(nodeRef); 353 bool notify = entryContained; 354 355 if (entry.IsDirectory()) { 356 // ignore the directory if it's already known 357 if (entry.GetNodeRef(&nodeRef) == B_OK 358 && _HasDirectory(nodeRef)) { 359 TRACE(" WE ALREADY HAVE DIR %s, %ld:%Ld\n", 360 name, nodeRef.device, nodeRef.node); 361 return; 362 } 363 364 // a new directory to watch for us 365 if ((!entryContained && !_CloserToPath(entry)) 366 || (parentContained && !_WatchRecursively()) 367 || _AddDirectory(entry, true) != B_OK 368 || _WatchFilesOnly()) 369 notify = parentContained; 370 // NOTE: entry is now toast after _AddDirectory() was called! 371 // Does not matter right now, but if it's a problem, use the node_ref 372 // version... 373 } else if (entryContained) { 374 TRACE(" NEW ENTRY PARENT CONTAINED: %d\n", parentContained); 375 _AddFile(entry); 376 } 377 378 if (notify && entryContained) { 379 message->AddBool("added", true); 380 // nodeRef is pointing to the parent directory 381 entry.GetNodeRef(&nodeRef); 382 _NotifyTarget(message, nodeRef); 383 } 384 } 385 386 387 void 388 PathHandler::_EntryRemoved(BMessage* message) 389 { 390 node_ref nodeRef; 391 uint64 directoryNode; 392 if (message->FindInt32("device", &nodeRef.device) != B_OK 393 || message->FindInt64("directory", (int64 *)&directoryNode) != B_OK 394 || message->FindInt64("node", &nodeRef.node) != B_OK) 395 return; 396 397 bool contained; 398 if (_HasDirectory(nodeRef, &contained)) { 399 // the directory has been removed, so we remove it as well 400 _RemoveDirectory(nodeRef, directoryNode); 401 if (contained && !_WatchFilesOnly()) { 402 message->AddBool("removed", true); 403 _NotifyTarget(message, nodeRef); 404 } 405 } else if (_HasFile(nodeRef)) { 406 message->AddBool("removed", true); 407 _NotifyTarget(message, nodeRef); 408 _RemoveFile(nodeRef); 409 } 410 } 411 412 413 void 414 PathHandler::_EntryMoved(BMessage* message) 415 { 416 // has the entry been moved into a monitored directory or has 417 // it been removed from one? 418 const char* name; 419 node_ref nodeRef; 420 uint64 fromNode; 421 uint64 node; 422 if (message->FindInt32("device", &nodeRef.device) != B_OK 423 || message->FindInt64("to directory", &nodeRef.node) != B_OK 424 || message->FindInt64("from directory", (int64 *)&fromNode) != B_OK 425 || message->FindInt64("node", (int64 *)&node) != B_OK 426 || message->FindString("name", &name) != B_OK) 427 return; 428 429 BEntry entry; 430 if (set_entry(nodeRef, name, entry) != B_OK) 431 return; 432 433 bool entryContained = _IsContained(entry); 434 bool wasAdded = false; 435 bool wasRemoved = false; 436 bool notify = false; 437 438 bool parentContained; 439 if (_HasDirectory(nodeRef, &parentContained)) { 440 // something has been added to our watched directories 441 442 nodeRef.node = node; 443 TRACE(" ADDED TO PARENT (%d), has entry %d/%d, entry %d %d\n", 444 parentContained, _HasDirectory(nodeRef), _HasFile(nodeRef), 445 entryContained, _CloserToPath(entry)); 446 447 if (entry.IsDirectory()) { 448 if (!_HasDirectory(nodeRef) 449 && (entryContained || _CloserToPath(entry))) { 450 // there is a new directory to watch for us 451 if (entryContained 452 || (parentContained && !_WatchRecursively())) { 453 _AddDirectory(entry, true); 454 // NOTE: entry is toast now! 455 } else if (_GetClosest(fPath.Path(), false, 456 nodeRef) == B_OK) { 457 // the new directory might put us even 458 // closer to the path we are after 459 _AddDirectory(nodeRef, true); 460 } 461 462 wasAdded = true; 463 notify = entryContained; 464 } 465 if (_WatchFilesOnly()) 466 notify = false; 467 } else if (!_HasFile(nodeRef) && entryContained) { 468 // file has been added 469 wasAdded = true; 470 notify = true; 471 _AddFile(entry); 472 } 473 } else { 474 // and entry has been removed from our directories 475 wasRemoved = true; 476 477 nodeRef.node = node; 478 if (entry.IsDirectory()) { 479 if (_HasDirectory(nodeRef, ¬ify)) 480 _RemoveDirectory(entry, fromNode); 481 if (_WatchFilesOnly()) 482 notify = false; 483 } else { 484 _RemoveFile(entry); 485 notify = true; 486 } 487 } 488 489 if (notify) { 490 if (wasAdded) 491 message->AddBool("added", true); 492 if (wasRemoved) 493 message->AddBool("removed", true); 494 495 _NotifyTarget(message, nodeRef); 496 } 497 } 498 499 500 void 501 PathHandler::MessageReceived(BMessage* message) 502 { 503 switch (message->what) { 504 case B_NODE_MONITOR: 505 { 506 int32 opcode; 507 if (message->FindInt32("opcode", &opcode) != B_OK) 508 return; 509 510 switch (opcode) { 511 case B_ENTRY_CREATED: 512 _EntryCreated(message); 513 break; 514 515 case B_ENTRY_REMOVED: 516 _EntryRemoved(message); 517 break; 518 519 case B_ENTRY_MOVED: 520 _EntryMoved(message); 521 break; 522 523 default: 524 _NotifyTarget(message); 525 break; 526 } 527 break; 528 } 529 530 default: 531 BHandler::MessageReceived(message); 532 break; 533 } 534 535 //#ifdef TRACE_PATH_MONITOR 536 // Dump(); 537 //#endif 538 } 539 540 541 bool 542 PathHandler::_IsContained(const node_ref& nodeRef) const 543 { 544 BDirectory directory(&nodeRef); 545 if (directory.InitCheck() != B_OK) 546 return false; 547 548 BEntry entry; 549 if (directory.GetEntry(&entry) != B_OK) 550 return false; 551 552 return _IsContained(entry); 553 } 554 555 556 bool 557 PathHandler::_IsContained(BEntry& entry) const 558 { 559 BPath path; 560 if (entry.GetPath(&path) != B_OK) 561 return false; 562 563 bool contained = strncmp(path.Path(), fPath.Path(), fPathLength) == 0; 564 if (!contained) 565 return false; 566 567 // Prevent the case that the entry is in another folder which happens 568 // to have the same substring for fPathLength chars, like: 569 // /path/we/are/watching 570 // /path/we/are/watching-not/subfolder/entry 571 // NOTE: We wouldn't be here if path.Path() was shorter than fPathLength, 572 // strncmp() catches that case. 573 const char* last = &path.Path()[fPathLength]; 574 if (last[0] && last[0] != '/') 575 return false; 576 577 return true; 578 } 579 580 581 bool 582 PathHandler::_HasDirectory(const node_ref& nodeRef, 583 bool* _contained /* = NULL */) const 584 { 585 WatchedDirectory directory; 586 directory.node = nodeRef; 587 588 DirectorySet::const_iterator iterator = fDirectories.find(directory); 589 if (iterator == fDirectories.end()) 590 return false; 591 592 if (_contained != NULL) 593 *_contained = iterator->contained; 594 return true; 595 } 596 597 598 bool 599 PathHandler::_CloserToPath(BEntry& entry) const 600 { 601 BPath path; 602 if (entry.GetPath(&path) != B_OK) 603 return false; 604 605 return strncmp(path.Path(), fPath.Path(), strlen(path.Path())) == 0; 606 } 607 608 609 void 610 PathHandler::_NotifyTarget(BMessage* message) const 611 { 612 // NOTE: This version is only used for B_STAT_CHANGED and B_ATTR_CHANGED 613 node_ref nodeRef; 614 if (message->FindInt32("device", &nodeRef.device) != B_OK 615 || message->FindInt64("node", &nodeRef.node) != B_OK) 616 return; 617 _NotifyTarget(message, nodeRef); 618 } 619 620 621 void 622 PathHandler::_NotifyTarget(BMessage* message, const node_ref& nodeRef) const 623 { 624 BMessage update(*message); 625 update.what = B_PATH_MONITOR; 626 627 TRACE("_NotifyTarget(): node ref %ld.%Ld\n", nodeRef.device, nodeRef.node); 628 629 WatchedDirectory directory; 630 directory.node = nodeRef; 631 632 DirectorySet::const_iterator iterator = fDirectories.find(directory); 633 if (iterator != fDirectories.end()) { 634 if (_WatchFilesOnly()) { 635 // stat or attr notification for a directory 636 return; 637 } 638 BDirectory nodeDirectory(&nodeRef); 639 BEntry entry; 640 if (nodeDirectory.GetEntry(&entry) == B_OK) { 641 BPath path(&entry); 642 update.AddString("path", path.Path()); 643 } 644 } else { 645 if (_WatchFoldersOnly()) { 646 // this is bound to be a notification for a file 647 return; 648 } 649 FileEntry setEntry; 650 setEntry.ref.device = nodeRef.device; 651 setEntry.node = nodeRef.node; 652 // name does not need to be set, since it's not used for comparing 653 FileSet::const_iterator i = fFiles.find(setEntry); 654 if (i != fFiles.end()) { 655 BPath path(&(i->ref)); 656 update.AddString("path", path.Path()); 657 } 658 } 659 660 // This is in case the target is interested in figuring out which 661 // BPathMonitor::StartWatching() call the message is resulting from. 662 update.AddString("watched_path", fPath.Path()); 663 664 fTarget.SendMessage(&update); 665 } 666 667 668 status_t 669 PathHandler::_AddDirectory(BEntry& entry, bool notify) 670 { 671 WatchedDirectory directory; 672 status_t status = entry.GetNodeRef(&directory.node); 673 if (status != B_OK) 674 return status; 675 676 #ifdef TRACE_PATH_MONITOR 677 { 678 BPath path(&entry); 679 TRACE(" ADD DIRECTORY %s, %ld:%Ld\n", 680 path.Path(), directory.node.device, directory.node.node); 681 } 682 #endif 683 684 // check if we are already know this directory 685 686 // TODO: It should be possible to ommit this check if we know it 687 // can't be the case (for example when adding subfolders recursively, 688 // although in that case, the API user may still have added this folder 689 // independently, so for now, it should be the safest to perform this 690 // check in all cases.) 691 if (_HasDirectory(directory.node)) 692 return B_OK; 693 694 directory.contained = _IsContained(entry); 695 696 uint32 flags; 697 if (directory.contained) 698 flags = (fFlags & WATCH_NODE_FLAG_MASK) | B_WATCH_DIRECTORY; 699 else 700 flags = B_WATCH_DIRECTORY; 701 702 status = watch_node(&directory.node, flags, this); 703 if (status != B_OK) 704 return status; 705 706 fDirectories.insert(directory); 707 708 if (_WatchRecursively()) { 709 BDirectory dir(&directory.node); 710 while (dir.GetNextEntry(&entry) == B_OK) { 711 if (entry.IsDirectory()) { 712 // and here is the recursion: 713 if (_AddDirectory(entry, notify) != B_OK) 714 break; 715 } else if (!_WatchFoldersOnly()) { 716 if (_AddFile(entry, notify) != B_OK) 717 break; 718 } 719 } 720 } 721 722 #if 0 723 BEntry parent; 724 if (entry.GetParent(&parent) == B_OK 725 && !_IsContained(parent)) { 726 // TODO: remove parent from watched directories 727 } 728 #endif 729 return B_OK; 730 } 731 732 733 status_t 734 PathHandler::_AddDirectory(node_ref& nodeRef, bool notify) 735 { 736 BDirectory directory(&nodeRef); 737 status_t status = directory.InitCheck(); 738 if (status == B_OK) { 739 BEntry entry; 740 status = directory.GetEntry(&entry); 741 if (status == B_OK) 742 status = _AddDirectory(entry, notify); 743 } 744 745 return status; 746 } 747 748 749 status_t 750 PathHandler::_RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode) 751 { 752 TRACE(" REMOVE DIRECTORY %ld:%Ld\n", nodeRef.device, nodeRef.node); 753 754 WatchedDirectory directory; 755 directory.node = nodeRef; 756 757 DirectorySet::iterator iterator = fDirectories.find(directory); 758 if (iterator == fDirectories.end()) 759 return B_ENTRY_NOT_FOUND; 760 761 watch_node(&directory.node, B_STOP_WATCHING, this); 762 763 node_ref directoryRef; 764 directoryRef.device = nodeRef.device; 765 directoryRef.node = directoryNode; 766 767 if (!_HasDirectory(directoryRef)) { 768 // we don't have the parent directory now, but we'll need it in order 769 // to find this directory again in case it's added again 770 if (_AddDirectory(directoryRef) != B_OK 771 && _GetClosest(fPath.Path(), false, directoryRef) == B_OK) 772 _AddDirectory(directoryRef); 773 } 774 775 fDirectories.erase(iterator); 776 777 // stop watching subdirectories and their files when in recursive mode 778 if (_WatchRecursively()) { 779 BDirectory entryDirectory(&nodeRef); 780 if (entryDirectory.InitCheck() == B_OK) { 781 // The directory still exists, but was moved outside our watched 782 // folder hierarchy. 783 _RemoveEntriesRecursively(entryDirectory); 784 } else { 785 // Actually, it shouldn't be possible to remove non-empty 786 // folders so for this case we don't need to do anything. We should 787 // have received remove notifications for all affected files and 788 // folders that used to live in this directory. 789 } 790 } 791 792 return B_OK; 793 } 794 795 796 status_t 797 PathHandler::_RemoveDirectory(BEntry& entry, ino_t directoryNode) 798 { 799 node_ref nodeRef; 800 status_t status = entry.GetNodeRef(&nodeRef); 801 if (status != B_OK) 802 return status; 803 804 return _RemoveDirectory(nodeRef, directoryNode); 805 } 806 807 808 bool 809 PathHandler::_HasFile(const node_ref& nodeRef) const 810 { 811 FileEntry setEntry; 812 setEntry.ref.device = nodeRef.device; 813 setEntry.node = nodeRef.node; 814 // name does not need to be set, since it's not used for comparing 815 FileSet::const_iterator iterator = fFiles.find(setEntry); 816 return iterator != fFiles.end(); 817 } 818 819 820 status_t 821 PathHandler::_AddFile(BEntry& entry, bool notify) 822 { 823 if ((fFlags & (WATCH_NODE_FLAG_MASK & ~B_WATCH_DIRECTORY)) == 0) 824 return B_OK; 825 826 #ifdef TRACE_PATH_MONITOR 827 { 828 BPath path(&entry); 829 TRACE(" ADD FILE %s\n", path.Path()); 830 } 831 #endif 832 833 node_ref nodeRef; 834 status_t status = entry.GetNodeRef(&nodeRef); 835 if (status != B_OK) 836 return status; 837 838 // check if we already know this file 839 840 // TODO: It should be possible to omit this check if we know it 841 // can't be the case (for example when adding subfolders recursively, 842 // although in that case, the API user may still have added this file 843 // independently, so for now, it should be the safest to perform this 844 // check in all cases.) 845 if (_HasFile(nodeRef)) 846 return B_OK; 847 848 status = watch_node(&nodeRef, (fFlags & WATCH_NODE_FLAG_MASK), this); 849 if (status != B_OK) 850 return status; 851 852 FileEntry setEntry; 853 entry.GetRef(&setEntry.ref); 854 setEntry.node = nodeRef.node; 855 856 fFiles.insert(setEntry); 857 858 if (notify && _WatchFilesOnly()) { 859 // We also notify our target about new files if it's only interested 860 // in files; it won't be notified about new directories, so it cannot 861 // know when to search for them. 862 BMessage update; 863 update.AddInt32("opcode", B_ENTRY_CREATED); 864 update.AddInt32("device", nodeRef.device); 865 update.AddInt64("directory", setEntry.ref.directory); 866 update.AddString("name", setEntry.ref.name); 867 update.AddBool("added", true); 868 869 _NotifyTarget(&update, nodeRef); 870 } 871 872 return B_OK; 873 } 874 875 876 status_t 877 PathHandler::_RemoveFile(const node_ref& nodeRef) 878 { 879 TRACE(" REMOVE FILE %ld:%Ld\n", nodeRef.device, nodeRef.node); 880 881 FileEntry setEntry; 882 setEntry.ref.device = nodeRef.device; 883 setEntry.node = nodeRef.node; 884 // name does not need to be set, since it's not used for comparing 885 FileSet::iterator iterator = fFiles.find(setEntry); 886 if (iterator == fFiles.end()) 887 return B_ENTRY_NOT_FOUND; 888 889 watch_node(&nodeRef, B_STOP_WATCHING, this); 890 fFiles.erase(iterator); 891 return B_OK; 892 } 893 894 895 status_t 896 PathHandler::_RemoveFile(BEntry& entry) 897 { 898 node_ref nodeRef; 899 status_t status = entry.GetNodeRef(&nodeRef); 900 if (status != B_OK) 901 return status; 902 903 return _RemoveFile(nodeRef); 904 } 905 906 907 void 908 PathHandler::_RemoveEntriesRecursively(BDirectory& directory) 909 { 910 node_ref directoryNode; 911 directory.GetNodeRef(&directoryNode); 912 913 BMessage message(B_PATH_MONITOR); 914 message.AddInt32("opcode", B_ENTRY_REMOVED); 915 // TODO: B_ENTRY_MOVED could be regarded as more correct, 916 // but then we would definitely need more information in this 917 // function. 918 message.AddInt32("device", directoryNode.device); 919 message.AddInt64("directory", directoryNode.node); 920 message.AddInt64("node", 0LL); 921 // dummy node, will be replaced by real node 922 923 // NOTE: The _NotifyTarget() gets the node id, but constructs 924 // the path to the previous location of the entry according to the file 925 // or folder in our sets. This makes it more expensive of course, but 926 // I have no inspiration for improvement at the moment. 927 928 BEntry entry; 929 while (directory.GetNextEntry(&entry) == B_OK) { 930 node_ref nodeRef; 931 if (entry.GetNodeRef(&nodeRef) != B_OK) { 932 fprintf(stderr, "PathHandler::_RemoveEntriesRecursively() - " 933 "failed to get node_ref\n"); 934 continue; 935 } 936 937 message.ReplaceInt64("node", nodeRef.node); 938 939 if (entry.IsDirectory()) { 940 // notification 941 if (!_WatchFilesOnly()) 942 _NotifyTarget(&message, nodeRef); 943 944 _RemoveDirectory(nodeRef, directoryNode.node); 945 BDirectory subDirectory(&entry); 946 _RemoveEntriesRecursively(subDirectory); 947 } else { 948 // notification 949 if (!_WatchFoldersOnly()) 950 _NotifyTarget(&message, nodeRef); 951 952 _RemoveFile(nodeRef); 953 } 954 } 955 } 956 957 958 // #pragma mark - 959 960 961 BPathMonitor::BPathMonitor() 962 { 963 } 964 965 966 BPathMonitor::~BPathMonitor() 967 { 968 } 969 970 971 /*static*/ status_t 972 BPathMonitor::_InitLockerIfNeeded() 973 { 974 static vint32 lock = 0; 975 976 if (sLocker != NULL) 977 return B_OK; 978 979 while (sLocker == NULL) { 980 if (atomic_add(&lock, 1) == 0) { 981 sLocker = new (nothrow) BLocker("path monitor"); 982 TRACE("Create PathMonitor locker\n"); 983 if (sLocker == NULL) 984 return B_NO_MEMORY; 985 } 986 snooze(5000); 987 } 988 989 return B_OK; 990 } 991 992 993 /*static*/ status_t 994 BPathMonitor::_InitLooperIfNeeded() 995 { 996 static vint32 lock = 0; 997 998 if (sLooper != NULL) 999 return B_OK; 1000 1001 while (sLooper == NULL) { 1002 if (atomic_add(&lock, 1) == 0) { 1003 // first thread initializes the global looper 1004 sLooper = new (nothrow) BLooper("PathMonitor looper"); 1005 TRACE("Start PathMonitor looper\n"); 1006 if (sLooper == NULL) 1007 return B_NO_MEMORY; 1008 thread_id thread = sLooper->Run(); 1009 if (thread < B_OK) 1010 return (status_t)thread; 1011 } 1012 snooze(5000); 1013 } 1014 1015 return sLooper->Thread() >= 0 ? B_OK : B_ERROR; 1016 } 1017 1018 1019 /*static*/ status_t 1020 BPathMonitor::StartWatching(const char* path, uint32 flags, BMessenger target) 1021 { 1022 TRACE("StartWatching(%s)\n", path); 1023 1024 status_t status = _InitLockerIfNeeded(); 1025 if (status != B_OK) 1026 return status; 1027 1028 // use the global looper for receiving node monitor notifications 1029 status = _InitLooperIfNeeded(); 1030 if (status < B_OK) 1031 return status; 1032 1033 BAutolock _(sLocker); 1034 1035 WatcherMap::iterator iterator = sWatchers.find(target); 1036 Watcher* watcher = NULL; 1037 if (iterator != sWatchers.end()) 1038 watcher = iterator->second; 1039 1040 PathHandler* handler = new (nothrow) PathHandler(path, flags, target, 1041 sLooper); 1042 if (handler == NULL) 1043 return B_NO_MEMORY; 1044 status = handler->InitCheck(); 1045 if (status < B_OK) { 1046 delete handler; 1047 return status; 1048 } 1049 1050 if (watcher == NULL) { 1051 watcher = new (nothrow) BPrivate::Watcher; 1052 if (watcher == NULL) { 1053 delete handler; 1054 return B_NO_MEMORY; 1055 } 1056 sWatchers[target] = watcher; 1057 } 1058 1059 watcher->handlers[path] = handler; 1060 return B_OK; 1061 } 1062 1063 1064 /*static*/ status_t 1065 BPathMonitor::StopWatching(const char* path, BMessenger target) 1066 { 1067 if (sLocker == NULL) 1068 return B_NO_INIT; 1069 1070 TRACE("StopWatching(%s)\n", path); 1071 1072 BAutolock _(sLocker); 1073 1074 WatcherMap::iterator iterator = sWatchers.find(target); 1075 if (iterator == sWatchers.end()) 1076 return B_BAD_VALUE; 1077 1078 Watcher* watcher = iterator->second; 1079 HandlerMap::iterator i = watcher->handlers.find(path); 1080 1081 if (i == watcher->handlers.end()) 1082 return B_BAD_VALUE; 1083 1084 PathHandler* handler = i->second; 1085 watcher->handlers.erase(i); 1086 1087 handler->Quit(); 1088 1089 if (watcher->handlers.empty()) { 1090 sWatchers.erase(iterator); 1091 delete watcher; 1092 } 1093 1094 return B_OK; 1095 } 1096 1097 1098 /*static*/ status_t 1099 BPathMonitor::StopWatching(BMessenger target) 1100 { 1101 if (sLocker == NULL) 1102 return B_NO_INIT; 1103 1104 BAutolock _(sLocker); 1105 1106 WatcherMap::iterator iterator = sWatchers.find(target); 1107 if (iterator == sWatchers.end()) 1108 return B_BAD_VALUE; 1109 1110 Watcher* watcher = iterator->second; 1111 while (!watcher->handlers.empty()) { 1112 HandlerMap::iterator i = watcher->handlers.begin(); 1113 PathHandler* handler = i->second; 1114 watcher->handlers.erase(i); 1115 1116 handler->Quit(); 1117 } 1118 1119 sWatchers.erase(iterator); 1120 delete watcher; 1121 1122 return B_OK; 1123 } 1124 1125 } // namespace BPrivate 1126