diff --git a/headers/private/storage/PathMonitor.h b/headers/private/storage/PathMonitor.h index 3309eb2b98..2a511ec40c 100644 --- a/headers/private/storage/PathMonitor.h +++ b/headers/private/storage/PathMonitor.h @@ -1,5 +1,5 @@ /* - * Copyright 2007-2013, Haiku Inc. All Rights Reserved. + * Copyright 2007-2013, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. */ #ifndef _PATH_MONITOR_H @@ -9,12 +9,23 @@ #include +// Monitoring a path always implies B_WATCH_NAME for the path itself. I.e. even +// if only B_WATCH_STAT is specified, B_ENTRY_{CREATED,MOVED,REMOVED} +// notifications are sent when the respective entry is created/moved/removed. + // additional flags (combined with those in NodeMonitor.h) -#define B_WATCH_FILES_ONLY 0x0100 -#define B_WATCH_RECURSIVELY 0x0200 +#define B_WATCH_RECURSIVELY 0x0100 + // Watch not only the entry specified by the path, but also recursively all + // descendents. A recursive B_WATCH_DIRECTORY is implied, i.e. + // B_ENTRY_{CREATED,MOVED,REMOVED} notifications will be sent for any entry + // change below the given path, unless explicitly suppressed by + // B_WATCH_{FILES,DIRECTORIES}_ONLY. +#define B_WATCH_FILES_ONLY 0x0200 + // A notification will only be sent when the node it concerns is not a + // directory. No effect in non-recursive mode. #define B_WATCH_DIRECTORIES_ONLY 0x0400 -// NOTE: B_WATCH_RECURSIVELY usually implies to watch for file changes as well, -// if that is not desired, add B_WATCH_DIRECTORIES_ONLY to the flags. + // A notification will only be sent when the node it concerns is a + // directory. No effect in non-recursive mode. #define B_PATH_MONITOR '_PMN' @@ -69,4 +80,7 @@ public: } // namespace BPrivate +using BPrivate::BPathMonitor; + + #endif // _PATH_MONITOR_H diff --git a/src/kits/storage/Jamfile b/src/kits/storage/Jamfile index 6a053c7ec2..dba6e60cc9 100644 --- a/src/kits/storage/Jamfile +++ b/src/kits/storage/Jamfile @@ -2,7 +2,7 @@ SubDir HAIKU_TOP src kits storage ; SetSubDirSupportedPlatforms haiku libbe_test ; -UsePrivateHeaders app libroot shared storage ; +UsePrivateHeaders app kernel libroot shared storage ; UsePrivateSystemHeaders ; # for libbe_test diff --git a/src/kits/storage/PathMonitor.cpp b/src/kits/storage/PathMonitor.cpp index a782171da2..1167c34e42 100644 --- a/src/kits/storage/PathMonitor.cpp +++ b/src/kits/storage/PathMonitor.cpp @@ -1,10 +1,11 @@ /* - * Copyright 2007-2013, Haiku Inc. All Rights Reserved. + * Copyright 2007-2013, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Axel Dörfler, axeld@pinc-software.de - * Stephan Aßmus + * Stephan Aßmus, superstippi@gmx.de + * Ingo Weinhold, ingo_weinhold@gmx.de */ @@ -26,217 +27,847 @@ #include #include +#include +#include #include +#include +#include #undef TRACE //#define TRACE_PATH_MONITOR #ifdef TRACE_PATH_MONITOR -# define TRACE(x...) debug_printf(x) +# define TRACE(...) debug_printf("BPathMonitor: " __VA_ARGS__) #else -# define TRACE(x...) ; +# define TRACE(...) ; #endif -using namespace BPrivate; -using namespace std; -using std::nothrow; // TODO: Remove this line if the above line is enough. +// TODO: Support symlink components in the path. +// TODO: Support mounting/unmounting of volumes in path components and within +// the watched path tree. -// TODO: Use optimizations where stuff is already known to avoid iterating -// the watched directory and files set too often. - #define WATCH_NODE_FLAG_MASK 0x00ff -namespace BPrivate { -struct FileEntry { - entry_ref ref; - ino_t node; -}; - -#if __GNUC__ > 3 - bool operator<(const FileEntry& a, const FileEntry& b); - class FileEntryLess : public binary_function - { - public: - bool operator() (const FileEntry& a, const FileEntry& b) const - { - return a < b; - } - }; - typedef set FileSet; -#else - typedef set FileSet; -#endif - -struct WatchedDirectory { - node_ref node; - bool contained; -}; -typedef set DirectorySet; +namespace { -class PathHandler; -typedef map HandlerMap; - -struct Watcher { - HandlerMap handlers; -}; -typedef map WatcherMap; - -class PathHandler : public BHandler { - public: - PathHandler(const char* path, uint32 flags, BMessenger target, - BLooper* looper); - virtual ~PathHandler(); - - status_t InitCheck() const; - void SetTarget(BMessenger target); - void Quit(); - - virtual void MessageReceived(BMessage* message); -#ifdef TRACE_PATH_MONITOR - void Dump(); -#endif - - private: - status_t _GetClosest(const char* path, bool updatePath, - node_ref& nodeRef); - - bool _WatchRecursively() const; - bool _WatchFilesOnly() const; - bool _WatchFoldersOnly() const; - - void _EntryCreated(BMessage* message); - void _EntryRemoved(BMessage* message); - void _EntryMoved(BMessage* message); - - bool _IsContained(const node_ref& nodeRef) const; - bool _IsContained(BEntry& entry) const; - bool _HasDirectory(const node_ref& nodeRef, - bool* _contained = NULL) const; - bool _CloserToPath(BEntry& entry) const; - - void _NotifyTarget(BMessage* message) const; - void _NotifyTarget(BMessage* message, const node_ref& nodeRef) const; - - status_t _AddDirectory(BEntry& entry, bool notify = false); - status_t _AddDirectory(node_ref& nodeRef, bool notify = false); - status_t _RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode); - status_t _RemoveDirectory(BEntry& entry, ino_t directoryNode); - - bool _HasFile(const node_ref& nodeRef, const entry_ref** _ref = NULL) - const; - status_t _AddFile(BEntry& entry, bool notify = false); - status_t _RemoveFile(const node_ref& nodeRef); - status_t _RemoveFile(BEntry& entry); - - void _RemoveEntriesRecursively(BDirectory& directory); - - BPath fPath; - int32 fPathLength; - BMessenger fTarget; - uint32 fFlags; - status_t fStatus; - DirectorySet fDirectories; - FileSet fFiles; -}; +struct Directory; +struct Node; +struct WatcherHashDefinition; +typedef BOpenHashTable WatcherMap; static pthread_once_t sInitOnce = PTHREAD_ONCE_INIT; -static WatcherMap sWatchers; +static WatcherMap* sWatchers = NULL; static BLocker* sLocker = NULL; static BLooper* sLooper = NULL; static BPathMonitor::BWatchingInterface* sDefaultWatchingInterface = NULL; static BPathMonitor::BWatchingInterface* sWatchingInterface = NULL; -static status_t -set_entry(const node_ref& nodeRef, const char* name, BEntry& entry) -{ - entry_ref ref; - ref.device = nodeRef.device; - ref.directory = nodeRef.node; - - status_t status = ref.set_name(name); - if (status != B_OK) - return status; - - return entry.SetTo(&ref, true); -} - - -bool -operator<(const FileEntry& a, const FileEntry& b) -{ - if (a.ref.device == b.ref.device && a.node < b.node) - return true; - if (a.ref.device < b.ref.device) - return true; - - return false; -} - - -bool -operator<(const node_ref& a, const node_ref& b) -{ - if (a.device == b.device && a.node < b.node) - return true; - if (a.device < b.device) - return true; - - return false; -} - - -bool -operator<(const WatchedDirectory& a, const WatchedDirectory& b) -{ - return a.node < b.node; -} - - // #pragma mark - -PathHandler::PathHandler(const char* path, uint32 flags, BMessenger target, - BLooper* looper) - : BHandler(path), - fTarget(target), - fFlags(flags) +/*! Returns empty path, if either \a parent or \a subPath is empty or an + allocation fails. + */ +static BString +make_path(const BString& parent, const char* subPath) { - if (path == NULL || !path[0]) { - fStatus = B_BAD_VALUE; + BString path = parent; + int32 length = path.Length(); + if (length == 0 || subPath[0] == '\0') + return BString(); + + if (parent.ByteAt(length - 1) != '/') { + path << '/'; + if (path.Length() < ++length) + return BString(); + } + + path << subPath; + if (path.Length() <= length) + return BString(); + return path; +} + + +// #pragma mark - Ancestor + + +class Ancestor { +public: + Ancestor(Ancestor* parent, const BString& path, size_t pathComponentOffset) + : + fParent(parent), + fChild(NULL), + fPath(path), + fEntryRef(-1, -1, fPath.String() + pathComponentOffset), + fNodeRef(), + fWatchingFlags(0), + fIsDirectory(false) + { + if (pathComponentOffset == 0) { + // must be "/" + fEntryRef.SetTo(-1, -1, "."); + } + + if (fParent != NULL) + fParent->fChild = this; + } + + Ancestor* Parent() const + { + return fParent; + } + + Ancestor* Child() const + { + return fChild; + } + + const BString& Path() const + { + return fPath; + } + + const char* Name() const + { + return fEntryRef.name; + } + + bool Exists() const + { + return fNodeRef.device >= 0; + } + + const NotOwningEntryRef& EntryRef() const + { + return fEntryRef; + } + + const node_ref& NodeRef() const + { + return fNodeRef; + } + + bool IsDirectory() const + { + return fIsDirectory; + } + + status_t StartWatching(uint32 pathFlags, BHandler* target) + { + // init entry ref + BEntry entry; + status_t error = entry.SetTo(fPath); + if (error != B_OK) + return error; + + entry_ref entryRef; + error = entry.GetRef(&entryRef); + if (error != B_OK) + return error; + + fEntryRef.device = entryRef.device; + fEntryRef.directory = entryRef.directory; + + // init node ref + struct stat st; + error = entry.GetStat(&st); + if (error != B_OK) + return error == B_ENTRY_NOT_FOUND ? B_OK : error; + + fNodeRef = node_ref(st.st_dev, st.st_ino); + fIsDirectory = S_ISDIR(st.st_mode); + + // start watching + uint32 flags = fChild == NULL ? pathFlags : B_WATCH_DIRECTORY; + // In theory B_WATCH_NAME would suffice for all existing ancestors, + // plus B_WATCH_DIRECTORY for the parent of the first not existing + // ancestor. In practice this complicates the transitions when an + // ancestor is created/removed/moved. + if (flags != 0) { + error = sWatchingInterface->WatchNode(&fNodeRef, flags, target); + TRACE(" started to watch ancestor %p (\"%s\", %#" B_PRIx32 + ") -> %s\n", this, Name(), flags, strerror(error)); + if (error != B_OK) + return error; + } + + fWatchingFlags = flags; + return B_OK; + } + + void StopWatching(BHandler* target) + { + // stop watching + if (fWatchingFlags != 0) { + sWatchingInterface->WatchNode(&fNodeRef, B_STOP_WATCHING, target); + fWatchingFlags = 0; + } + + // uninitialize node and entry ref + fIsDirectory = false; + fNodeRef = node_ref(); + fEntryRef.SetDirectoryNodeRef(node_ref()); + } + + Ancestor*& HashNext() + { + return fHashNext; + } + +private: + Ancestor* fParent; + Ancestor* fChild; + Ancestor* fHashNext; + BString fPath; + NotOwningEntryRef fEntryRef; + node_ref fNodeRef; + uint32 fWatchingFlags; + bool fIsDirectory; +}; + + +// #pragma mark - AncestorMap + + +struct AncestorHashDefinition { + typedef node_ref KeyType; + typedef Ancestor ValueType; + + size_t HashKey(const node_ref& key) const + { + return size_t(key.device ^ key.node); + } + + size_t Hash(Ancestor* value) const + { + return HashKey(value->NodeRef()); + } + + bool Compare(const node_ref& key, Ancestor* value) const + { + return key == value->NodeRef(); + } + + Ancestor*& GetLink(Ancestor* value) const + { + return value->HashNext(); + } +}; + + +typedef BOpenHashTable AncestorMap; + + +// #pragma mark - Entry + + +class Entry : public SinglyLinkedListLinkImpl { +public: + Entry(Directory* parent, const BString& name, ::Node* node) + : + fParent(parent), + fName(name), + fNode(node) + { + } + + Directory* Parent() const + { + return fParent; + } + + const BString& Name() const + { + return fName; + } + + ::Node* Node() const + { + return fNode; + } + + void SetNode(::Node* node) + { + fNode = node; + } + + inline NotOwningEntryRef EntryRef() const; + + Entry*& HashNext() + { + return fHashNext; + } + +private: + Directory* fParent; + BString fName; + ::Node* fNode; + Entry* fHashNext; +}; + +typedef SinglyLinkedList EntryList; + + +// EntryMap + + +struct EntryHashDefinition { + typedef const char* KeyType; + typedef Entry ValueType; + + size_t HashKey(const char* key) const + { + return BString::HashValue(key); + } + + size_t Hash(Entry* value) const + { + return value->Name().HashValue(); + } + + bool Compare(const char* key, Entry* value) const + { + return value->Name() == key; + } + + Entry*& GetLink(Entry* value) const + { + return value->HashNext(); + } +}; + + +typedef BOpenHashTable EntryMap; + + +// #pragma mark - Node + + +class Node { +public: + Node(const node_ref& nodeRef) + : + fNodeRef(nodeRef) + { + } + + virtual ~Node() + { + } + + virtual bool IsDirectory() const + { + return false; + } + + virtual Directory* ToDirectory() + { + return NULL; + } + + const node_ref& NodeRef() const + { + return fNodeRef; + } + + const EntryList& Entries() const + { + return fEntries; + } + + bool HasEntries() const + { + return !fEntries.IsEmpty(); + } + + Entry* FirstNodeEntry() const + { + return fEntries.Head(); + } + + bool IsOnlyNodeEntry(Entry* entry) const + { + return entry == fEntries.Head() && fEntries.GetNext(entry) == NULL; + } + + void AddNodeEntry(Entry* entry) + { + fEntries.Add(entry); + } + + void RemoveNodeEntry(Entry* entry) + { + fEntries.Remove(entry); + } + + Node*& HashNext() + { + return fHashNext; + } + +private: + node_ref fNodeRef; + EntryList fEntries; + Node* fHashNext; +}; + + +struct NodeHashDefinition { + typedef node_ref KeyType; + typedef Node ValueType; + + size_t HashKey(const node_ref& key) const + { + return size_t(key.device ^ key.node); + } + + size_t Hash(Node* value) const + { + return HashKey(value->NodeRef()); + } + + bool Compare(const node_ref& key, Node* value) const + { + return key == value->NodeRef(); + } + + Node*& GetLink(Node* value) const + { + return value->HashNext(); + } +}; + + +typedef BOpenHashTable NodeMap; + + +// #pragma mark - Directory + + +class Directory : public Node { +public: + static Directory* Create(const node_ref& nodeRef) + { + Directory* directory = new(std::nothrow) Directory(nodeRef); + if (directory == NULL || directory->fEntries.Init() != B_OK) { + delete directory; + return NULL; + } + + return directory; + } + + virtual bool IsDirectory() const + { + return true; + } + + virtual Directory* ToDirectory() + { + return this; + } + + Entry* FindEntry(const char* name) const + { + return fEntries.Lookup(name); + } + + Entry* CreateEntry(const BString& name, Node* node) + { + Entry* entry = new(std::nothrow) Entry(this, name, node); + if (entry == NULL || entry->Name().IsEmpty()) { + delete entry; + return NULL; + } + + AddEntry(entry); + return entry; + } + + void AddEntry(Entry* entry) + { + fEntries.Insert(entry); + } + + void RemoveEntry(Entry* entry) + { + fEntries.Remove(entry); + } + + EntryMap::Iterator GetEntryIterator() const + { + return fEntries.GetIterator(); + } + + Entry* RemoveAllEntries() + { + return fEntries.Clear(true); + } + +private: + Directory(const node_ref& nodeRef) + : + Node(nodeRef) + { + } + +private: + EntryMap fEntries; +}; + + +// #pragma mark - Entry + + +inline NotOwningEntryRef +Entry::EntryRef() const +{ + return NotOwningEntryRef(fParent->NodeRef(), fName); +} + + +// #pragma mark - PathHandler + + +class PathHandler : public BHandler { +public: + PathHandler(const char* path, uint32 flags, + const BMessenger& target, BLooper* looper); + virtual ~PathHandler(); + + status_t InitCheck() const; + void Quit(); + + const BString& OriginalPath() const + { return fOriginalPath; } + uint32 Flags() const { return fFlags; } + + virtual void MessageReceived(BMessage* message); + + PathHandler*& HashNext() { return fHashNext; } + +private: + status_t _CreateAncestors(); + status_t _StartWatchingAncestors(Ancestor* ancestor, + bool notify); + void _StopWatchingAncestors(Ancestor* ancestor, + bool notify); + + void _EntryCreated(BMessage* message); + void _EntryRemoved(BMessage* message); + void _EntryMoved(BMessage* message); + void _NodeChanged(BMessage* message); + + bool _EntryCreated(const NotOwningEntryRef& entryRef, + const node_ref& nodeRef, bool isDirectory, + bool dryRun, bool notify, Entry** _entry); + bool _EntryRemoved(const NotOwningEntryRef& entryRef, + const node_ref& nodeRef, bool dryRun, + bool notify, Entry** _keepEntry); + + bool _CheckDuplicateEntryNotification(int32 opcode, + const entry_ref& toEntryRef, + const node_ref& nodeRef, + const entry_ref* fromEntryRef = NULL); + void _UnsetDuplicateEntryNotification(); + + Ancestor* _GetAncestor(const node_ref& nodeRef) const; + + status_t _AddNode(const node_ref& nodeRef, + bool isDirectory, bool notify, + Entry* entry = NULL, Node** _node = NULL); + void _DeleteNode(Node* node, bool notify); + Node* _GetNode(const node_ref& nodeRef) const; + + status_t _AddEntryIfNeeded(Directory* directory, + const char* name, const node_ref& nodeRef, + bool isDirectory, bool notify, + Entry** _entry = NULL); + void _DeleteEntry(Entry* entry, bool notify); + void _DeleteEntryAlreadyRemovedFromParent( + Entry* entry, bool notify); + + void _NotifyFilesCreatedOrRemoved(Entry* entry, + int32 opcode) const; + void _NotifyEntryCreatedOrRemoved(Entry* entry, + int32 opcode) const; + void _NotifyEntryCreatedOrRemoved( + const entry_ref& entryRef, + const node_ref& nodeRef, const char* path, + bool isDirectory, int32 opcode) const; + void _NotifyEntryMoved(const entry_ref& fromEntryRef, + const entry_ref& toEntryRef, + const node_ref& nodeRef, const char* path, + bool isDirectory, bool wasAdded, + bool wasRemoved) const; + void _NotifyTarget(BMessage& message, + const char* path) const; + + BString _NodePath(const Node* node) const; + BString _EntryPath(const Entry* entry) const; + + + bool _WatchRecursively() const; + bool _WatchFilesOnly() const; + bool _WatchDirectoriesOnly() const; + +private: + BMessenger fTarget; + uint32 fFlags; + status_t fStatus; + BString fOriginalPath; + BString fPath; + Ancestor* fRoot; + Ancestor* fBaseAncestor; + Node* fBaseNode; + AncestorMap fAncestors; + NodeMap fNodes; + PathHandler* fHashNext; + int32 fDuplicateEntryNotificationOpcode; + node_ref fDuplicateEntryNotificationNodeRef; + entry_ref fDuplicateEntryNotificationToEntryRef; + entry_ref fDuplicateEntryNotificationFromEntryRef; +}; + + +struct PathHandlerHashDefinition { + typedef const char* KeyType; + typedef PathHandler ValueType; + + size_t HashKey(const char* key) const + { + return BString::HashValue(key); + } + + size_t Hash(PathHandler* value) const + { + return value->OriginalPath().HashValue(); + } + + bool Compare(const char* key, PathHandler* value) const + { + return key == value->OriginalPath(); + } + + PathHandler*& GetLink(PathHandler* value) const + { + return value->HashNext(); + } +}; + + +typedef BOpenHashTable PathHandlerMap; + + +// #pragma mark - Watcher + + +struct Watcher : public PathHandlerMap { + static Watcher* Create(const BMessenger& target) + { + Watcher* watcher = new(std::nothrow) Watcher(target); + if (watcher == NULL || watcher->Init() != B_OK) { + delete watcher; + return NULL; + } + return watcher; + } + + const BMessenger& Target() const + { + return fTarget; + } + + Watcher*& HashNext() + { + return fHashNext; + } + +private: + Watcher(const BMessenger& target) + : + fTarget(target) + { + } + +private: + BMessenger fTarget; + Watcher* fHashNext; +}; + + +struct WatcherHashDefinition { + typedef BMessenger KeyType; + typedef Watcher ValueType; + + size_t HashKey(const BMessenger& key) const + { + return key.HashValue(); + } + + size_t Hash(Watcher* value) const + { + return HashKey(value->Target()); + } + + bool Compare(const BMessenger& key, Watcher* value) const + { + return key == value->Target(); + } + + Watcher*& GetLink(Watcher* value) const + { + return value->HashNext(); + } +}; + + +// #pragma mark - PathHandler + + +PathHandler::PathHandler(const char* path, uint32 flags, + const BMessenger& target, BLooper* looper) + : + BHandler(path), + fTarget(target), + fFlags(flags), + fStatus(B_OK), + fOriginalPath(path), + fPath(), + fRoot(NULL), + fBaseAncestor(NULL), + fBaseNode(NULL), + fAncestors(), + fNodes() +{ + TRACE("%p->PathHandler::PathHandler(\"%s\", %#" B_PRIx32 ")\n", this, path, + flags); + + _UnsetDuplicateEntryNotification(); + + fStatus = fAncestors.Init(); + if (fStatus != B_OK) + return; + + fStatus = fNodes.Init(); + if (fStatus != B_OK) + return; + + // normalize the flags + if ((fFlags & B_WATCH_RECURSIVELY) != 0) { + // We add B_WATCH_NAME and B_WATCH_DIRECTORY as needed, so clear them + // here. + fFlags &= ~uint32(B_WATCH_NAME | B_WATCH_DIRECTORY); + } else { + // The B_WATCH_*_ONLY flags are only valid for the recursive mode. + // B_WATCH_NAME is implied (we watch the parent directory). + fFlags &= ~uint32(B_WATCH_FILES_ONLY | B_WATCH_DIRECTORIES_ONLY + | B_WATCH_NAME); + } + + // Normalize the path a bit. We can't use BPath, as it may really normalize + // the path, i.e. resolve symlinks and such, which may cause us to monitor + // the wrong path. We want some normalization, though: + // * relative -> absolute path + // * fold duplicate '/'s + // * omit "." components + // * fail when encountering ".." components + + // make absolute + BString normalizedPath; + if (path[0] == '/') { + normalizedPath = "/"; + path++; + } else + normalizedPath = BPath(".").Path(); + if (normalizedPath.IsEmpty()) { + fStatus = B_NO_MEMORY; return; } - // TODO: support watching not-yet-mounted volumes as well! - node_ref nodeRef; - fStatus = _GetClosest(path, true, nodeRef); - if (fStatus < B_OK) + // parse path components + const char* pathEnd = path + strlen(path); + for (;;) { + // skip '/'s + while (path[0] == '/') + path++; + if (path == pathEnd) + break; + + const char* componentEnd = strchr(path, '/'); + if (componentEnd == NULL) + componentEnd = pathEnd; + size_t componentLength = componentEnd - path; + + // handle ".' and ".." + if (path[0] == '.') { + if (componentLength == 1) { + path = componentEnd; + continue; + } + if (componentLength == 2 && path[1] == '.') { + fStatus = B_BAD_VALUE; + return; + } + } + + int32 normalizedPathLength = normalizedPath.Length(); + if (normalizedPath.ByteAt(normalizedPathLength - 1) != '/') { + normalizedPath << '/'; + normalizedPathLength++; + } + normalizedPath.Append(path, componentEnd - path); + normalizedPathLength += int32(componentEnd - path); + + if (normalizedPath.Length() != normalizedPathLength) { + fStatus = B_NO_MEMORY; + return; + } + + path = componentEnd; + } + + fPath = normalizedPath; + + // Create the Ancestor objects -- they correspond to the path components and + // are used for watching changes that affect the entries on the path. + fStatus = _CreateAncestors(); + if (fStatus != B_OK) return; - TRACE("PathHandler: %s\n", path); - + // add ourselves to the looper if (!looper->Lock()) debugger("PathHandler: failed to lock the looper"); looper->AddHandler(this); looper->Unlock(); - fStatus = _AddDirectory(nodeRef); - - // TODO: work-around for existing files (should not watch the directory in - // this case) - BEntry entry(path); - if (entry.Exists() && !entry.IsDirectory()) - _AddFile(entry); + // start watching + fStatus = _StartWatchingAncestors(fRoot, false); + if (fStatus != B_OK) + return; } PathHandler::~PathHandler() { + TRACE("%p->PathHandler::~PathHandler(\"%s\", %#" B_PRIx32 ")\n", this, + fPath.String(), fFlags); + + if (fBaseNode != NULL) + _DeleteNode(fBaseNode, false); + + while (fRoot != NULL) { + Ancestor* nextAncestor = fRoot->Child(); + delete fRoot; + fRoot = nextAncestor; + } } @@ -251,262 +882,16 @@ void PathHandler::Quit() { if (sLooper->Lock()) { + TRACE("%p->PathHandler::Quit()\n", this); sWatchingInterface->StopWatching(this); sLooper->RemoveHandler(this); sLooper->Unlock(); - } + } else + TRACE("%p->PathHandler::Quit(): failed to lock looper\n", this); delete this; } -#ifdef TRACE_PATH_MONITOR -void -PathHandler::Dump() -{ - TRACE("WATCHING DIRECTORIES:\n"); - DirectorySet::iterator i = fDirectories.begin(); - for (; i != fDirectories.end(); i++) { - TRACE(" %ld:%Ld (%s)\n", i->node.device, i->node.node, i->contained - ? "contained" : "-"); - } - - TRACE("WATCHING FILES:\n"); - - FileSet::iterator j = fFiles.begin(); - for (; j != fFiles.end(); j++) { - TRACE(" %ld:%Ld\n", j->ref.device, j->node); - } -} -#endif - - -status_t -PathHandler::_GetClosest(const char* path, bool updatePath, node_ref& nodeRef) -{ - BPath first(path); - BString missing; - - while (true) { - // try to find the first part of the path that exists - BDirectory directory; - status_t status = directory.SetTo(first.Path()); - if (status == B_OK) { - status = directory.GetNodeRef(&nodeRef); - if (status == B_OK) { - if (updatePath) { - // normalize path - status = fPath.SetTo(&directory, NULL, true); - if (status == B_OK) { - fPath.Append(missing.String()); - fPathLength = strlen(fPath.Path()); - } - } - return status; - } - } - - if (updatePath) { - if (missing.Length() > 0) - missing.Prepend("/"); - missing.Prepend(first.Leaf()); - } - - if (first.GetParent(&first) != B_OK) - return B_ERROR; - } -} - - -bool -PathHandler::_WatchRecursively() const -{ - return (fFlags & B_WATCH_RECURSIVELY) != 0; -} - - -bool -PathHandler::_WatchFilesOnly() const -{ - return (fFlags & B_WATCH_FILES_ONLY) != 0; -} - - -bool -PathHandler::_WatchFoldersOnly() const -{ - return (fFlags & B_WATCH_DIRECTORIES_ONLY) != 0; -} - - -void -PathHandler::_EntryCreated(BMessage* message) -{ - const char* name; - node_ref nodeRef; - if (message->FindInt32("device", &nodeRef.device) != B_OK - || message->FindInt64("directory", &nodeRef.node) != B_OK - || message->FindString("name", &name) != B_OK) { - TRACE("PathHandler::_EntryCreated() - malformed message!\n"); - return; - } - - BEntry entry; - if (set_entry(nodeRef, name, entry) != B_OK) { - TRACE("PathHandler::_EntryCreated() - set_entry failed!\n"); - return; - } - - bool parentContained = false; - bool entryContained = _IsContained(entry); - if (entryContained) - parentContained = _IsContained(nodeRef); - bool notify = entryContained; - - if (entry.IsDirectory()) { - // ignore the directory if it's already known - if (entry.GetNodeRef(&nodeRef) == B_OK - && _HasDirectory(nodeRef)) { - TRACE(" WE ALREADY HAVE DIR %s, %ld:%Ld\n", - name, nodeRef.device, nodeRef.node); - return; - } - - // a new directory to watch for us - if ((!entryContained && !_CloserToPath(entry)) - || (parentContained && !_WatchRecursively()) - || _AddDirectory(entry, true) != B_OK - || _WatchFilesOnly()) - notify = parentContained; - // NOTE: entry is now toast after _AddDirectory() was called! - // Does not matter right now, but if it's a problem, use the node_ref - // version... - } else if (entryContained) { - TRACE(" NEW ENTRY PARENT CONTAINED: %d\n", parentContained); - _AddFile(entry); - } - - if (notify && entryContained) { - message->AddBool("added", true); - // nodeRef is pointing to the parent directory - entry.GetNodeRef(&nodeRef); - _NotifyTarget(message, nodeRef); - } -} - - -void -PathHandler::_EntryRemoved(BMessage* message) -{ - node_ref nodeRef; - uint64 directoryNode; - if (message->FindInt32("device", &nodeRef.device) != B_OK - || message->FindInt64("directory", (int64 *)&directoryNode) != B_OK - || message->FindInt64("node", &nodeRef.node) != B_OK) - return; - - bool contained; - if (_HasDirectory(nodeRef, &contained)) { - // the directory has been removed, so we remove it as well - _RemoveDirectory(nodeRef, directoryNode); - if (contained && !_WatchFilesOnly()) { - message->AddBool("removed", true); - _NotifyTarget(message, nodeRef); - } - } else if (_HasFile(nodeRef)) { - message->AddBool("removed", true); - _NotifyTarget(message, nodeRef); - _RemoveFile(nodeRef); - } -} - - -void -PathHandler::_EntryMoved(BMessage* message) -{ - // has the entry been moved into a monitored directory or has - // it been removed from one? - const char* name; - node_ref nodeRef; - uint64 fromNode; - uint64 node; - if (message->FindInt32("device", &nodeRef.device) != B_OK - || message->FindInt64("to directory", &nodeRef.node) != B_OK - || message->FindInt64("from directory", (int64 *)&fromNode) != B_OK - || message->FindInt64("node", (int64 *)&node) != B_OK - || message->FindString("name", &name) != B_OK) - return; - - BEntry entry; - if (set_entry(nodeRef, name, entry) != B_OK) - return; - - bool entryContained = _IsContained(entry); - bool wasAdded = false; - bool wasRemoved = false; - bool notify = false; - - bool parentContained; - if (_HasDirectory(nodeRef, &parentContained)) { - // something has been added to our watched directories - - nodeRef.node = node; - TRACE(" ADDED TO PARENT (%d), has entry %d/%d, entry %d %d\n", - parentContained, _HasDirectory(nodeRef), _HasFile(nodeRef), - entryContained, _CloserToPath(entry)); - - if (entry.IsDirectory()) { - if (!_HasDirectory(nodeRef) - && (entryContained || _CloserToPath(entry))) { - // there is a new directory to watch for us - if (entryContained - || (parentContained && !_WatchRecursively())) { - _AddDirectory(entry, true); - // NOTE: entry is toast now! - } else if (_GetClosest(fPath.Path(), false, - nodeRef) == B_OK) { - // the new directory might put us even - // closer to the path we are after - _AddDirectory(nodeRef, true); - } - - wasAdded = true; - notify = entryContained; - } - if (_WatchFilesOnly()) - notify = false; - } else if (!_HasFile(nodeRef) && entryContained) { - // file has been added - wasAdded = true; - notify = true; - _AddFile(entry); - } - } else { - // and entry has been removed from our directories - wasRemoved = true; - - nodeRef.node = node; - if (entry.IsDirectory()) { - if (_HasDirectory(nodeRef, ¬ify)) - _RemoveDirectory(entry, fromNode); - if (_WatchFilesOnly()) - notify = false; - } else { - _RemoveFile(entry); - notify = true; - } - } - - if (notify) { - if (wasAdded) - message->AddBool("added", true); - if (wasRemoved) - message->AddBool("removed", true); - - _NotifyTarget(message, nodeRef); - } -} - - void PathHandler::MessageReceived(BMessage* message) { @@ -531,9 +916,11 @@ PathHandler::MessageReceived(BMessage* message) break; default: - _NotifyTarget(message); + _UnsetDuplicateEntryNotification(); + _NodeChanged(message); break; } + break; } @@ -541,431 +928,1034 @@ PathHandler::MessageReceived(BMessage* message) BHandler::MessageReceived(message); break; } - -//#ifdef TRACE_PATH_MONITOR -// Dump(); -//#endif } -bool -PathHandler::_IsContained(const node_ref& nodeRef) const +status_t +PathHandler::_CreateAncestors() { - BDirectory directory(&nodeRef); - if (directory.InitCheck() != B_OK) - return false; + TRACE("%p->PathHandler::_CreateAncestors()\n", this); + + // create the Ancestor objects + const char* path = fPath.String(); + const char* pathEnd = path + fPath.Length(); + const char* component = path; + + Ancestor* ancestor = NULL; + + while (component < pathEnd) { + const char* componentEnd = component == path + ? component + 1 : strchr(component, '/'); + if (componentEnd == NULL) + componentEnd = pathEnd; + + BString ancestorPath(path, componentEnd - path); + if (ancestorPath.IsEmpty()) + return B_NO_MEMORY; + + ancestor = new(std::nothrow) Ancestor(ancestor, ancestorPath, + component - path); + TRACE(" created ancestor %p (\"%s\" / \"%s\")\n", ancestor, + ancestor->Path().String(), ancestor->Name()); + if (ancestor == NULL) + return B_NO_MEMORY; + + if (fRoot == NULL) + fRoot = ancestor; + + component = componentEnd[0] == '/' ? componentEnd + 1 : componentEnd; + } + + fBaseAncestor = ancestor; + + return B_OK; +} + + +status_t +PathHandler::_StartWatchingAncestors(Ancestor* startAncestor, bool notify) +{ + TRACE("%p->PathHandler::_StartWatchingAncestors(%p, %d)\n", this, + startAncestor, notify); + + // The watch flags for the path (if it exists). Recursively implies + // directory, since we need to watch the entries. + uint32 watchFlags = (fFlags & WATCH_NODE_FLAG_MASK) + | (_WatchRecursively() ? B_WATCH_DIRECTORY : 0); + + for (Ancestor* ancestor = startAncestor; ancestor != NULL; + ancestor = ancestor->Child()) { + status_t error = ancestor->StartWatching(watchFlags, this); + if (error != B_OK) + return error; + + if (!ancestor->Exists()) { + TRACE(" -> ancestor doesn't exist\n"); + break; + } + + fAncestors.Insert(ancestor); + } + + if (!fBaseAncestor->Exists()) + return B_OK; + + if (notify) { + _NotifyEntryCreatedOrRemoved(fBaseAncestor->EntryRef(), + fBaseAncestor->NodeRef(), fPath, fBaseAncestor->IsDirectory(), + B_ENTRY_CREATED); + } + + if (!_WatchRecursively()) + return B_OK; + + status_t error = _AddNode(fBaseAncestor->NodeRef(), + fBaseAncestor->IsDirectory(), notify && _WatchFilesOnly(), NULL, + &fBaseNode); + if (error != B_OK) + return error; + + return B_OK; +} + + +void +PathHandler::_StopWatchingAncestors(Ancestor* ancestor, bool notify) +{ + // stop watching the tree below path + if (fBaseNode != NULL) { + _DeleteNode(fBaseNode, notify && _WatchFilesOnly()); + fBaseNode = NULL; + } + + if (notify && fBaseAncestor->Exists() + && (fBaseAncestor->IsDirectory() + ? !_WatchFilesOnly() : !_WatchDirectoriesOnly())) { + _NotifyEntryCreatedOrRemoved(fBaseAncestor->EntryRef(), + fBaseAncestor->NodeRef(), fPath, fBaseAncestor->IsDirectory(), + B_ENTRY_REMOVED); + } + + // stop watching the ancestors and uninitialize their entries + for (; ancestor != NULL; ancestor = ancestor->Child()) { + if (ancestor->Exists()) + fAncestors.Remove(ancestor); + ancestor->StopWatching(this); + } +} + + +void +PathHandler::_EntryCreated(BMessage* message) +{ + // TODO: Unless we're watching files only, we might want to forward (some + // of) the messages that don't agree with our model, since our client + // maintains its model at a different time and the notification might be + // necessary to keep it up-to-date. E.g. consider the following case: + // 1. a directory is created + // 2. a file is created in the directory + // 3. the file is removed from the directory + // If we get the notification after 1. and before 2., we pass it on to the + // client, which may get it after 2. and before 3., thus seeing the file. + // If we then get the entry-created notification after 3., we don't see the + // file anymore and ignore the notification as well as the following + // entry-removed notification. That is the client will never know that the + // file has been removed. This can only happen in recursive mode. Otherwise + // (and with B_WATCH_DIRECTORY) we just pass on all notifications. + // A possible solution could be to just create a zombie entry and pass on + // the entry-created notification. We wouldn't be able to adhere to the + // B_WATCH_FILES_ONLY/B_WATCH_DIRECTORIES_ONLY flags, but that should be + // acceptable. Either the client hasn't seen the entry either -- then it + // doesn't matter -- or it likely has ignored a not matching entry anyway. + + NotOwningEntryRef entryRef; + node_ref nodeRef; + + if (message->FindInt32("device", &nodeRef.device) != B_OK + || message->FindInt64("node", &nodeRef.node) != B_OK + || message->FindInt64("directory", &entryRef.directory) != B_OK + || message->FindString("name", (const char**)&entryRef.name) != B_OK) { + return; + } + entryRef.device = nodeRef.device; + + if (_CheckDuplicateEntryNotification(B_ENTRY_CREATED, entryRef, nodeRef)) + return; + + TRACE("%p->PathHandler::_EntryCreated(): entry: %" B_PRIdDEV ":%" B_PRIdINO + ":\"%s\", node: %" B_PRIdDEV ":%" B_PRIdINO "\n", this, entryRef.device, + entryRef.directory, entryRef.name, nodeRef.device, nodeRef.node); BEntry entry; - if (directory.GetEntry(&entry) != B_OK) - return false; - - return _IsContained(entry); -} - - -bool -PathHandler::_IsContained(BEntry& entry) const -{ - BPath path; - if (entry.GetPath(&path) != B_OK) - return false; - - bool contained = strncmp(path.Path(), fPath.Path(), fPathLength) == 0; - if (!contained) - return false; - - // Prevent the case that the entry is in another folder which happens - // to have the same substring for fPathLength chars, like: - // /path/we/are/watching - // /path/we/are/watching-not/subfolder/entry - // NOTE: We wouldn't be here if path.Path() was shorter than fPathLength, - // strncmp() catches that case. - const char* last = &path.Path()[fPathLength]; - if (last[0] && last[0] != '/') - return false; - - return true; -} - - -bool -PathHandler::_HasDirectory(const node_ref& nodeRef, - bool* _contained /* = NULL */) const -{ - WatchedDirectory directory; - directory.node = nodeRef; - - DirectorySet::const_iterator iterator = fDirectories.find(directory); - if (iterator == fDirectories.end()) - return false; - - if (_contained != NULL) - *_contained = iterator->contained; - return true; -} - - -bool -PathHandler::_CloserToPath(BEntry& entry) const -{ - BPath path; - if (entry.GetPath(&path) != B_OK) - return false; - - return strncmp(path.Path(), fPath.Path(), strlen(path.Path())) == 0; -} - - -void -PathHandler::_NotifyTarget(BMessage* message) const -{ - // NOTE: This version is only used for B_STAT_CHANGED and B_ATTR_CHANGED - node_ref nodeRef; - if (message->FindInt32("device", &nodeRef.device) != B_OK - || message->FindInt64("node", &nodeRef.node) != B_OK) + struct stat st; + if (entry.SetTo(&entryRef) != B_OK || entry.GetStat(&st) != B_OK + || nodeRef != node_ref(st.st_dev, st.st_ino)) { return; - _NotifyTarget(message, nodeRef); + } + + _EntryCreated(entryRef, nodeRef, S_ISDIR(st.st_mode), false, true, NULL); } void -PathHandler::_NotifyTarget(BMessage* message, const node_ref& nodeRef) const +PathHandler::_EntryRemoved(BMessage* message) { - BMessage update(*message); - update.what = B_PATH_MONITOR; + NotOwningEntryRef entryRef; + node_ref nodeRef; - TRACE("_NotifyTarget(): node ref %ld.%Ld\n", nodeRef.device, nodeRef.node); + if (message->FindInt32("device", &nodeRef.device) != B_OK + || message->FindInt64("node", &nodeRef.node) != B_OK + || message->FindInt64("directory", &entryRef.directory) != B_OK + || message->FindString("name", (const char**)&entryRef.name) != B_OK) { + return; + } + entryRef.device = nodeRef.device; + + if (_CheckDuplicateEntryNotification(B_ENTRY_REMOVED, entryRef, nodeRef)) + return; + + TRACE("%p->PathHandler::_EntryRemoved(): entry: %" B_PRIdDEV ":%" B_PRIdINO + ":\"%s\", node: %" B_PRIdDEV ":%" B_PRIdINO "\n", this, entryRef.device, + entryRef.directory, entryRef.name, nodeRef.device, nodeRef.node); + + _EntryRemoved(entryRef, nodeRef, false, true, NULL); +} + + +void +PathHandler::_EntryMoved(BMessage* message) +{ + NotOwningEntryRef fromEntryRef; + NotOwningEntryRef toEntryRef; + node_ref nodeRef; + + if (message->FindInt32("node device", &nodeRef.device) != B_OK + || message->FindInt64("node", &nodeRef.node) != B_OK + || message->FindInt32("device", &fromEntryRef.device) != B_OK + || message->FindInt64("from directory", &fromEntryRef.directory) != B_OK + || message->FindInt64("to directory", &toEntryRef.directory) != B_OK + || message->FindString("from name", (const char**)&fromEntryRef.name) + != B_OK + || message->FindString("name", (const char**)&toEntryRef.name) + != B_OK) { + return; + } + toEntryRef.device = fromEntryRef.device; + + if (_CheckDuplicateEntryNotification(B_ENTRY_MOVED, toEntryRef, nodeRef, + &fromEntryRef)) { + return; + } + + TRACE("%p->PathHandler::_EntryMoved(): entry: %" B_PRIdDEV ":%" B_PRIdINO + ":\"%s\" -> %" B_PRIdDEV ":%" B_PRIdINO ":\"%s\", node: %" B_PRIdDEV + ":%" B_PRIdINO "\n", this, fromEntryRef.device, fromEntryRef.directory, + fromEntryRef.name, toEntryRef.device, toEntryRef.directory, + toEntryRef.name, nodeRef.device, nodeRef.node); + + BEntry entry; + struct stat st; + if (entry.SetTo(&toEntryRef) != B_OK || entry.GetStat(&st) != B_OK + || nodeRef != node_ref(st.st_dev, st.st_ino)) { + _EntryRemoved(fromEntryRef, nodeRef, false, true, NULL); + return; + } + bool isDirectory = S_ISDIR(st.st_mode); + + Ancestor* fromAncestor = _GetAncestor(fromEntryRef.DirectoryNodeRef()); + Ancestor* toAncestor = _GetAncestor(toEntryRef.DirectoryNodeRef()); + + if (_WatchRecursively()) { + Node* fromDirectoryNode = _GetNode(fromEntryRef.DirectoryNodeRef()); + Node* toDirectoryNode = _GetNode(toEntryRef.DirectoryNodeRef()); + if (fromDirectoryNode != NULL || toDirectoryNode != NULL) { + // Check whether _EntryRemoved()/_EntryCreated() can handle the + // respective entry regularly (i.e. don't encounter an out-of-sync + // issue) or don't need to be called at all (entry outside the + // monitored tree). + if ((fromDirectoryNode == NULL + || _EntryRemoved(fromEntryRef, nodeRef, true, false, NULL)) + && (toDirectoryNode == NULL + || _EntryCreated(toEntryRef, nodeRef, isDirectory, true, + false, NULL))) { + // The entries can be handled regularly. We delegate the work to + // _EntryRemoved() and _EntryCreated() and only handle the + // notification ourselves. + + // handle removed + Entry* removedEntry = NULL; + if (fromDirectoryNode != NULL) { + _EntryRemoved(fromEntryRef, nodeRef, false, false, + &removedEntry); + } + + // handle created + Entry* createdEntry = NULL; + if (toDirectoryNode != NULL) { + _EntryCreated(toEntryRef, nodeRef, isDirectory, false, + false, &createdEntry); + } + + // notify + if (_WatchFilesOnly() && isDirectory) { + // recursively iterate through the removed and created + // hierarchy and send notifications for the files + if (removedEntry != NULL) { + _NotifyFilesCreatedOrRemoved(removedEntry, + B_ENTRY_REMOVED); + } + + if (createdEntry != NULL) { + _NotifyFilesCreatedOrRemoved(createdEntry, + B_ENTRY_CREATED); + } + } else { + BString path; + if (toDirectoryNode != NULL) { + path = make_path(_NodePath(toDirectoryNode), + toEntryRef.name); + } + _NotifyEntryMoved(fromEntryRef, toEntryRef, nodeRef, + path, isDirectory, fromDirectoryNode == NULL, + toDirectoryNode == NULL); + } + + if (removedEntry != NULL) + _DeleteEntry(removedEntry, false); + } else { + // The entries can't be handled regularly. We delegate all the + // work to _EntryRemoved() and _EntryCreated(). This will + // generate separate entry-removed and entry-created + // notifications. + + // handle removed + if (fromDirectoryNode != NULL) + _EntryRemoved(fromEntryRef, nodeRef, false, true, NULL); + + // handle created + if (toDirectoryNode != NULL) { + _EntryCreated(toEntryRef, nodeRef, isDirectory, false, true, + NULL); + } + } - if (_HasDirectory(nodeRef)) { - if (_WatchFilesOnly()) { - // stat or attr notification for a directory return; } - BDirectory nodeDirectory(&nodeRef); - BEntry entry; - if (nodeDirectory.GetEntry(&entry) == B_OK) { - BPath path(&entry); - update.AddString("path", path.Path()); + + if (fromAncestor == fBaseAncestor || toAncestor == fBaseAncestor) { + // That should never happen, as we should have found a matching + // directory node in this case. +#ifdef DEBUG + debugger("path ancestor exists, but doesn't have a directory"); + // Could actually be an out-of-memory situation, if we simply failed + // to create the directory earlier. +#endif + _StopWatchingAncestors(fRoot, false); + _StartWatchingAncestors(fRoot, false); + return; } } else { - if (_WatchFoldersOnly()) { - // this is bound to be a notification for a file + // Non-recursive mode: This notification is only of interest to us, if + // it is either a move into/within/out of the path and B_WATCH_DIRECTORY + // is set, or an ancestor might be affected. + if (fromAncestor == NULL && toAncestor == NULL) + return; + + if (fromAncestor == fBaseAncestor || toAncestor == fBaseAncestor) { + if ((fFlags & B_WATCH_DIRECTORY) != 0) { + BString path; + if (toAncestor == fBaseAncestor) + path = make_path(fPath, toEntryRef.name); + + _NotifyEntryMoved(fromEntryRef, toEntryRef, nodeRef, + path, isDirectory, fromAncestor == NULL, + toAncestor == NULL); + } return; } - - const entry_ref* entryRef; - if (_HasFile(nodeRef, &entryRef)) { - BPath path(entryRef); - update.AddString("path", path.Path()); - } } - // This is in case the target is interested in figuring out which - // BPathMonitor::StartWatching() call the message is resulting from. - update.AddString("watched_path", fPath.Path()); + if (fromAncestor == NULL && toAncestor == NULL) + return; - fTarget.SendMessage(&update); -} - - -status_t -PathHandler::_AddDirectory(BEntry& entry, bool notify) -{ - WatchedDirectory directory; - status_t status = entry.GetNodeRef(&directory.node); - if (status != B_OK) - return status; - -#ifdef TRACE_PATH_MONITOR -{ - BPath path(&entry); - TRACE(" ADD DIRECTORY %s, %ld:%Ld\n", - path.Path(), directory.node.device, directory.node.node); -} -#endif - - // check if we are already know this directory - - // TODO: It should be possible to ommit this check if we know it - // can't be the case (for example when adding subfolders recursively, - // although in that case, the API user may still have added this folder - // independently, so for now, it should be the safest to perform this - // check in all cases.) - if (_HasDirectory(directory.node)) - return B_OK; - - directory.contained = _IsContained(entry); - - uint32 flags; - if (directory.contained) - flags = (fFlags & WATCH_NODE_FLAG_MASK) | B_WATCH_DIRECTORY; - else - flags = B_WATCH_DIRECTORY; - - status = sWatchingInterface->WatchNode(&directory.node, flags, this); - if (status != B_OK) - return status; - - fDirectories.insert(directory); - - if (_WatchRecursively()) { - BDirectory dir(&directory.node); - while (dir.GetNextEntry(&entry) == B_OK) { - if (entry.IsDirectory()) { - // and here is the recursion: - if (_AddDirectory(entry, notify) != B_OK) - break; - } else if (!_WatchFoldersOnly()) { - if (_AddFile(entry, notify) != B_OK) - break; - } - } + if (fromAncestor == NULL) { + _EntryCreated(toEntryRef, nodeRef, isDirectory, false, true, NULL); + return; } -#if 0 - BEntry parent; - if (entry.GetParent(&parent) == B_OK - && !_IsContained(parent)) { - // TODO: remove parent from watched directories - } -#endif - return B_OK; -} - - -status_t -PathHandler::_AddDirectory(node_ref& nodeRef, bool notify) -{ - BDirectory directory(&nodeRef); - status_t status = directory.InitCheck(); - if (status == B_OK) { - BEntry entry; - status = directory.GetEntry(&entry); - if (status == B_OK) - status = _AddDirectory(entry, notify); + if (toAncestor == NULL) { + _EntryRemoved(fromEntryRef, nodeRef, false, true, NULL); + return; } - return status; -} - - -status_t -PathHandler::_RemoveDirectory(const node_ref& nodeRef, ino_t directoryNode) -{ - TRACE(" REMOVE DIRECTORY %ld:%Ld\n", nodeRef.device, nodeRef.node); - - WatchedDirectory directory; - directory.node = nodeRef; - - DirectorySet::iterator iterator = fDirectories.find(directory); - if (iterator == fDirectories.end()) - return B_ENTRY_NOT_FOUND; - - sWatchingInterface->WatchNode(&directory.node, B_STOP_WATCHING, this); - - node_ref directoryRef; - directoryRef.device = nodeRef.device; - directoryRef.node = directoryNode; - - if (!_HasDirectory(directoryRef)) { - // we don't have the parent directory now, but we'll need it in order - // to find this directory again in case it's added again - if (_AddDirectory(directoryRef) != B_OK - && _GetClosest(fPath.Path(), false, directoryRef) == B_OK) - _AddDirectory(directoryRef); - } - - fDirectories.erase(iterator); - - // stop watching subdirectories and their files when in recursive mode - if (_WatchRecursively()) { - BDirectory entryDirectory(&nodeRef); - if (entryDirectory.InitCheck() == B_OK) { - // The directory still exists, but was moved outside our watched - // folder hierarchy. - _RemoveEntriesRecursively(entryDirectory); + // An entry was moved in a true ancestor directory or between true ancestor + // directories. Unless the moved entry was or becomes our base ancestor, we + // let _EntryRemoved() and _EntryCreated() handle it. + bool fromIsBase = fromAncestor == fBaseAncestor->Parent() + && strcmp(fromEntryRef.name, fBaseAncestor->Name()) == 0; + bool toIsBase = toAncestor == fBaseAncestor->Parent() + && strcmp(toEntryRef.name, fBaseAncestor->Name()) == 0; + if (fromIsBase || toIsBase) { + // This might be a duplicate notification. Check whether our model + // already reflects the change. Otherwise stop/start watching the base + // ancestor as required. + bool notifyFilesRecursively = _WatchFilesOnly() && isDirectory; + if (fromIsBase) { + if (!fBaseAncestor->Exists()) + return; + _StopWatchingAncestors(fBaseAncestor, notifyFilesRecursively); } else { - // Actually, it shouldn't be possible to remove non-empty - // folders so for this case we don't need to do anything. We should - // have received remove notifications for all affected files and - // folders that used to live in this directory. + if (fBaseAncestor->Exists()) { + if (fBaseAncestor->NodeRef() == nodeRef + && isDirectory == fBaseAncestor->IsDirectory()) { + return; + } + + // We're out of sync with reality. + _StopWatchingAncestors(fBaseAncestor, true); + _StartWatchingAncestors(fBaseAncestor, true); + return; + } + + _StartWatchingAncestors(fBaseAncestor, notifyFilesRecursively); } + + if (!notifyFilesRecursively) { + _NotifyEntryMoved(fromEntryRef, toEntryRef, nodeRef, fPath, + isDirectory, toIsBase, fromIsBase); + } + return; } - return B_OK; -} - - -status_t -PathHandler::_RemoveDirectory(BEntry& entry, ino_t directoryNode) -{ - node_ref nodeRef; - status_t status = entry.GetNodeRef(&nodeRef); - if (status != B_OK) - return status; - - return _RemoveDirectory(nodeRef, directoryNode); -} - - -bool -PathHandler::_HasFile(const node_ref& nodeRef, - const entry_ref** _ref /*= NULL*/) const -{ - FileEntry setEntry; - setEntry.ref.device = nodeRef.device; - setEntry.node = nodeRef.node; - // name does not need to be set, since it's not used for comparing - FileSet::const_iterator iterator = fFiles.find(setEntry); - if (iterator == fFiles.end()) - return false; - - if (_ref != NULL) - *_ref = &iterator->ref; - return true; -} - - -status_t -PathHandler::_AddFile(BEntry& entry, bool notify) -{ - if ((fFlags & (WATCH_NODE_FLAG_MASK & ~B_WATCH_DIRECTORY)) == 0) - return B_OK; - -#ifdef TRACE_PATH_MONITOR -{ - BPath path(&entry); - TRACE(" ADD FILE %s\n", path.Path()); -} -#endif - - node_ref nodeRef; - status_t status = entry.GetNodeRef(&nodeRef); - if (status != B_OK) - return status; - - // check if we already know this file - - // TODO: It should be possible to omit this check if we know it - // can't be the case (for example when adding subfolders recursively, - // although in that case, the API user may still have added this file - // independently, so for now, it should be the safest to perform this - // check in all cases.) - if (_HasFile(nodeRef)) - return B_OK; - - status = sWatchingInterface->WatchNode(&nodeRef, - (fFlags & WATCH_NODE_FLAG_MASK), this); - if (status != B_OK) - return status; - - FileEntry setEntry; - entry.GetRef(&setEntry.ref); - setEntry.node = nodeRef.node; - - fFiles.insert(setEntry); - - if (notify && _WatchFilesOnly()) { - // We also notify our target about new files if it's only interested - // in files; it won't be notified about new directories, so it cannot - // know when to search for them. - BMessage update; - update.AddInt32("opcode", B_ENTRY_CREATED); - update.AddInt32("device", nodeRef.device); - update.AddInt64("directory", setEntry.ref.directory); - update.AddString("name", setEntry.ref.name); - update.AddBool("added", true); - - _NotifyTarget(&update, nodeRef); - } - - return B_OK; -} - - -status_t -PathHandler::_RemoveFile(const node_ref& nodeRef) -{ - TRACE(" REMOVE FILE %ld:%Ld\n", nodeRef.device, nodeRef.node); - - FileEntry setEntry; - setEntry.ref.device = nodeRef.device; - setEntry.node = nodeRef.node; - // name does not need to be set, since it's not used for comparing - FileSet::iterator iterator = fFiles.find(setEntry); - if (iterator == fFiles.end()) - return B_ENTRY_NOT_FOUND; - - sWatchingInterface->WatchNode(&nodeRef, B_STOP_WATCHING, this); - fFiles.erase(iterator); - return B_OK; -} - - -status_t -PathHandler::_RemoveFile(BEntry& entry) -{ - node_ref nodeRef; - status_t status = entry.GetNodeRef(&nodeRef); - if (status != B_OK) - return status; - - return _RemoveFile(nodeRef); + _EntryRemoved(fromEntryRef, nodeRef, false, true, NULL); + _EntryCreated(toEntryRef, nodeRef, isDirectory, false, true, NULL); } void -PathHandler::_RemoveEntriesRecursively(BDirectory& directory) +PathHandler::_NodeChanged(BMessage* message) { - node_ref directoryNode; - directory.GetNodeRef(&directoryNode); + node_ref nodeRef; - BMessage message(B_PATH_MONITOR); - message.AddInt32("opcode", B_ENTRY_REMOVED); - // TODO: B_ENTRY_MOVED could be regarded as more correct, - // but then we would definitely need more information in this - // function. - message.AddInt32("device", directoryNode.device); - message.AddInt64("directory", directoryNode.node); - message.AddInt64("node", 0LL); - // dummy node, will be replaced by real node - - // NOTE: The _NotifyTarget() gets the node id, but constructs - // the path to the previous location of the entry according to the file - // or folder in our sets. This makes it more expensive of course, but - // I have no inspiration for improvement at the moment. - - BEntry entry; - while (directory.GetNextEntry(&entry) == B_OK) { - node_ref nodeRef; - if (entry.GetNodeRef(&nodeRef) != B_OK) { - fprintf(stderr, "PathHandler::_RemoveEntriesRecursively() - " - "failed to get node_ref\n"); - continue; - } - - message.ReplaceInt64("node", nodeRef.node); - - if (entry.IsDirectory()) { - // notification - if (!_WatchFilesOnly()) - _NotifyTarget(&message, nodeRef); - - _RemoveDirectory(nodeRef, directoryNode.node); - BDirectory subDirectory(&entry); - _RemoveEntriesRecursively(subDirectory); - } else { - // notification - if (!_WatchFoldersOnly()) - _NotifyTarget(&message, nodeRef); - - _RemoveFile(nodeRef); - } + if (message->FindInt32("device", &nodeRef.device) != B_OK + || message->FindInt64("node", &nodeRef.node) != B_OK) { + return; } + + TRACE("%p->PathHandler::_NodeChanged(): node: %" B_PRIdDEV ":%" B_PRIdINO + ", %s%s\n", this, nodeRef.device, nodeRef.node, + message->GetInt32("opcode", B_STAT_CHANGED) == B_ATTR_CHANGED + ? "attribute: " : "stat", + message->GetInt32("opcode", B_STAT_CHANGED) == B_ATTR_CHANGED + ? message->GetString("attr", "") : ""); + + bool isDirectory = false; + BString path; + if (Ancestor* ancestor = _GetAncestor(nodeRef)) { + if (ancestor != fBaseAncestor) + return; + isDirectory = ancestor->IsDirectory(); + path = fPath; + } else if (Node* node = _GetNode(nodeRef)) { + isDirectory = node->IsDirectory(); + path = _NodePath(node); + } else + return; + + if (isDirectory ? _WatchFilesOnly() : _WatchDirectoriesOnly()) + return; + + _NotifyTarget(*message, path); } -// #pragma mark - +bool +PathHandler::_EntryCreated(const NotOwningEntryRef& entryRef, + const node_ref& nodeRef, bool isDirectory, bool dryRun, bool notify, + Entry** _entry) +{ + if (_entry != NULL) + *_entry = NULL; + + Ancestor* ancestor = _GetAncestor(nodeRef); + if (ancestor != NULL) { + if (isDirectory == ancestor->IsDirectory() + && entryRef == ancestor->EntryRef()) { + // just a duplicate notification + TRACE(" -> we already know the ancestor\n"); + return true; + } + + struct stat ancestorStat; + if (BEntry(&ancestor->EntryRef()).GetStat(&ancestorStat) == B_OK + && node_ref(ancestorStat.st_dev, ancestorStat.st_ino) + == ancestor->NodeRef() + && S_ISDIR(ancestorStat.st_mode) == ancestor->IsDirectory()) { + // Our information for the ancestor is up-to-date, so ignore the + // notification. + TRACE(" -> we know a different ancestor, but our info is " + "up-to-date\n"); + return true; + } + + // We're out of sync with reality. + TRACE(" -> ancestor mismatch -> resyncing\n"); + if (!dryRun) { + _StopWatchingAncestors(ancestor, true); + _StartWatchingAncestors(ancestor, true); + } + return false; + } + + ancestor = _GetAncestor(entryRef.DirectoryNodeRef()); + if (ancestor != NULL) { + if (ancestor != fBaseAncestor) { + // The directory is a true ancestor -- the notification is only of + // interest, if the entry matches the child ancestor. + Ancestor* childAncestor = ancestor->Child(); + if (strcmp(entryRef.name, childAncestor->Name()) != 0) { + TRACE(" -> not an ancestor entry we're interested in " + "(\"%s\")\n", childAncestor->Name()); + return true; + } + + if (!dryRun) { + if (childAncestor->Exists()) { + TRACE(" ancestor entry mismatch -> resyncing\n"); + // We're out of sync with reality -- the new entry refers to + // a different node. + _StopWatchingAncestors(childAncestor, true); + } + + TRACE(" -> starting to watch newly appeared ancestor\n"); + _StartWatchingAncestors(childAncestor, true); + } + return false; + } + + // The directory is our path. If watching recursively, just fall + // through. Otherwise, we want to pass on the notification, if directory + // watching is enabled. + if (!_WatchRecursively()) { + if ((fFlags & B_WATCH_DIRECTORY) != 0) { + _NotifyEntryCreatedOrRemoved(entryRef, nodeRef, + make_path(fPath, entryRef.name), isDirectory, + B_ENTRY_CREATED); + } + return true; + } + } + + if (!_WatchRecursively()) { + // That shouldn't happen, since we only watch the ancestors in this + // case. + return true; + } + + Node* directoryNode = _GetNode(entryRef.DirectoryNodeRef()); + if (directoryNode == NULL) + return true; + + Directory* directory = directoryNode->ToDirectory(); + if (directory == NULL) { + // We're out of sync with reality. + if (!dryRun) { + if (Entry* nodeEntry = directory->FirstNodeEntry()) { + // remove the entry that is in the way and re-add the proper + // entry + NotOwningEntryRef directoryEntryRef = nodeEntry->EntryRef(); + BString directoryName = nodeEntry->Name(); + _DeleteEntry(nodeEntry, true); + _EntryCreated(directoryEntryRef, entryRef.DirectoryNodeRef(), + true, false, notify, NULL); + } else { + // It's either the base node or something's severely fishy. + // Resync the whole path. + _StopWatchingAncestors(fBaseAncestor, true); + _StartWatchingAncestors(fBaseAncestor, true); + } + } + + return false; + } + + // Check, if there's a colliding entry. + if (Entry* nodeEntry = directory->FindEntry(entryRef.name)) { + Node* entryNode = nodeEntry->Node(); + if (entryNode->NodeRef() == nodeRef) + return true; + + // We're out of sync with reality -- the new entry refers to a different + // node. + _DeleteEntry(nodeEntry, true); + } + + if (dryRun) + return true; + + _AddEntryIfNeeded(directory, entryRef.name, nodeRef, isDirectory, notify, + _entry); + return true; +} + + +bool +PathHandler::_EntryRemoved(const NotOwningEntryRef& entryRef, + const node_ref& nodeRef, bool dryRun, bool notify, Entry** _keepEntry) +{ + if (_keepEntry != NULL) + *_keepEntry = NULL; + + Ancestor* ancestor = _GetAncestor(nodeRef); + if (ancestor != NULL) { + // The node is an ancestor. If this is a true match, stop watching the + // ancestor. + if (!ancestor->Exists()) + return true; + + if (entryRef != ancestor->EntryRef()) { + // We might be out of sync with reality -- the new entry refers to a + // different node. + struct stat ancestorStat; + if (BEntry(&ancestor->EntryRef()).GetStat(&ancestorStat) != B_OK) { + if (!dryRun) + _StopWatchingAncestors(ancestor, true); + return false; + } + + if (node_ref(ancestorStat.st_dev, ancestorStat.st_ino) + != ancestor->NodeRef() + || S_ISDIR(ancestorStat.st_mode) != ancestor->IsDirectory()) { + if (!dryRun) { + _StopWatchingAncestors(ancestor, true); + _StartWatchingAncestors(ancestor, true); + } + return false; + } + return true; + } + + if (!dryRun) + _StopWatchingAncestors(ancestor, true); + return false; + } + + ancestor = _GetAncestor(entryRef.DirectoryNodeRef()); + if (ancestor != NULL) { + if (ancestor != fBaseAncestor) { + // The directory is a true ancestor -- the notification cannot be + // of interest, since the node didn't match a known ancestor. + return true; + } + + // The directory is our path. If watching recursively, just fall + // through. Otherwise, we want to pass on the notification, if directory + // watching is enabled. + if (!_WatchRecursively()) { + if (notify && (fFlags & B_WATCH_DIRECTORY) != 0) { + _NotifyEntryCreatedOrRemoved(entryRef, nodeRef, + make_path(fPath, entryRef.name), false, B_ENTRY_REMOVED); + // We don't know whether this was a directory, but it + // doesn't matter in this case. + } + return true; + } + } + + if (!_WatchRecursively()) { + // That shouldn't happen, since we only watch the ancestors in this + // case. + return true; + } + + Node* directoryNode = _GetNode(entryRef.DirectoryNodeRef()); + if (directoryNode == NULL) { + // We shouldn't get a notification, if we don't known the directory. + return true; + } + + Directory* directory = directoryNode->ToDirectory(); + if (directory == NULL) { + // We might be out of sync with reality or the notification is just + // late. The former case is extremely unlikely (we are watching the node + // and its parent directory after all) and rather hard to verify. + return true; + } + + Entry* nodeEntry = directory->FindEntry(entryRef.name); + if (nodeEntry == NULL) { + // might be a non-directory node while we're in directories-only mode + return true; + } + + if (!dryRun) { + if (_keepEntry != NULL) + *_keepEntry = nodeEntry; + else + _DeleteEntry(nodeEntry, notify); + } + return true; +} + + +bool +PathHandler::_CheckDuplicateEntryNotification(int32 opcode, + const entry_ref& toEntryRef, const node_ref& nodeRef, + const entry_ref* fromEntryRef) +{ + if (opcode == fDuplicateEntryNotificationOpcode + && nodeRef == fDuplicateEntryNotificationNodeRef + && toEntryRef == fDuplicateEntryNotificationToEntryRef + && (fromEntryRef == NULL + || *fromEntryRef == fDuplicateEntryNotificationFromEntryRef)) { + return true; + } + + fDuplicateEntryNotificationOpcode = opcode; + fDuplicateEntryNotificationNodeRef = nodeRef; + fDuplicateEntryNotificationToEntryRef = toEntryRef; + fDuplicateEntryNotificationFromEntryRef = fromEntryRef != NULL + ? *fromEntryRef : entry_ref(); + return false; +} + + +void +PathHandler::_UnsetDuplicateEntryNotification() +{ + fDuplicateEntryNotificationOpcode = B_STAT_CHANGED; + fDuplicateEntryNotificationNodeRef = node_ref(); + fDuplicateEntryNotificationFromEntryRef = entry_ref(); + fDuplicateEntryNotificationToEntryRef = entry_ref(); +} + + +Ancestor* +PathHandler::_GetAncestor(const node_ref& nodeRef) const +{ + return fAncestors.Lookup(nodeRef); +} + + +status_t +PathHandler::_AddNode(const node_ref& nodeRef, bool isDirectory, bool notify, + Entry* entry, Node** _node) +{ + TRACE("%p->PathHandler::_AddNode(%" B_PRIdDEV ":%" B_PRIdINO + ", isDirectory: %d, notify: %d)\n", this, nodeRef.device, nodeRef.node, + isDirectory, notify); + + // If hard links are supported, we may already know the node. + Node* node = _GetNode(nodeRef); + if (node != NULL) { + if (entry != NULL) { + entry->SetNode(node); + node->AddNodeEntry(entry); + } + + if (_node != NULL) + *_node = node; + return B_OK; + } + + // create the node + Directory* directoryNode = NULL; + if (isDirectory) + node = directoryNode = Directory::Create(nodeRef); + else + node = new(std::nothrow) Node(nodeRef); + + if (node == NULL) + return B_NO_MEMORY; + + ObjectDeleter nodeDeleter(node); + + // start watching (don't do that for the base node, since we watch it + // already via fBaseAncestor) + if (nodeRef != fBaseAncestor->NodeRef()) { + uint32 flags = (fFlags & WATCH_NODE_FLAG_MASK) | B_WATCH_DIRECTORY; + status_t error = sWatchingInterface->WatchNode(&nodeRef, flags, this); + if (error != B_OK) + return error; + } + + fNodes.Insert(nodeDeleter.Detach()); + + if (entry != NULL) { + entry->SetNode(node); + node->AddNodeEntry(entry); + } + + if (_node != NULL) + *_node = node; + + if (!isDirectory) + return B_OK; + + // recursively add the directory's descendents + BDirectory directory; + if (directory.SetTo(&nodeRef) != B_OK) { + if (_node != NULL) + *_node = node; + return B_OK; + } + + entry_ref entryRef; + while (directory.GetNextRef(&entryRef) == B_OK) { + struct stat st; + if (BEntry(&entryRef).GetStat(&st) != B_OK) + continue; + + bool isDirectory = S_ISDIR(st.st_mode); + status_t error = _AddEntryIfNeeded(directoryNode, entryRef.name, + node_ref(st.st_dev, st.st_ino), isDirectory, notify); + if (error != B_OK) { + TRACE("%p->PathHandler::_AddNode(%" B_PRIdDEV ":%" B_PRIdINO + ", isDirectory: %d, notify: %d): failed to add directory " + "entry: \"%s\"\n", this, nodeRef.device, nodeRef.node, + isDirectory, notify, entryRef.name); + continue; + } + } + + return B_OK; +} + + +void +PathHandler::_DeleteNode(Node* node, bool notify) +{ + if (Directory* directory = node->ToDirectory()) { + Entry* entry = directory->RemoveAllEntries(); + while (entry != NULL) { + Entry* nextEntry = entry->HashNext(); + _DeleteEntryAlreadyRemovedFromParent(entry, notify); + entry = nextEntry; + } + } + + if (node->NodeRef() != fBaseAncestor->NodeRef()) + sWatchingInterface->WatchNode(&node->NodeRef(), B_STOP_WATCHING, this); + + fNodes.Remove(node); + delete node; +} + + +Node* +PathHandler::_GetNode(const node_ref& nodeRef) const +{ + return fNodes.Lookup(nodeRef); +} + + +status_t +PathHandler::_AddEntryIfNeeded(Directory* directory, const char* name, + const node_ref& nodeRef, bool isDirectory, bool notify, + Entry** _entry) +{ + TRACE("%p->PathHandler::_AddEntryIfNeeded(%" B_PRIdDEV ":%" B_PRIdINO + ":\"%s\", %" B_PRIdDEV ":%" B_PRIdINO + ", isDirectory: %d, notify: %d)\n", this, directory->NodeRef().device, + directory->NodeRef().node, name, nodeRef.device, nodeRef.node, + isDirectory, notify); + + if (!isDirectory && _WatchDirectoriesOnly()) { + if (_entry != NULL) + *_entry = NULL; + return B_OK; + } + + Entry* entry = directory->CreateEntry(name, NULL); + if (entry == NULL) + return B_NO_MEMORY; + + status_t error = _AddNode(nodeRef, isDirectory, notify && _WatchFilesOnly(), + entry); + if (error != B_OK) { + directory->RemoveEntry(entry); + delete entry; + return error; + } + + if (notify) + _NotifyEntryCreatedOrRemoved(entry, B_ENTRY_CREATED); + + if (_entry != NULL) + *_entry = entry; + return B_OK; +} + + +void +PathHandler::_DeleteEntry(Entry* entry, bool notify) +{ + entry->Parent()->RemoveEntry(entry); + _DeleteEntryAlreadyRemovedFromParent(entry, notify); +} + + +void +PathHandler::_DeleteEntryAlreadyRemovedFromParent(Entry* entry, bool notify) +{ + if (notify) + _NotifyEntryCreatedOrRemoved(entry, B_ENTRY_REMOVED); + + Node* node = entry->Node(); + if (node->IsOnlyNodeEntry(entry)) + _DeleteNode(node, notify && _WatchFilesOnly()); + + delete entry; +} + + +void +PathHandler::_NotifyFilesCreatedOrRemoved(Entry* entry, int32 opcode) const +{ + Directory* directory = entry->Node()->ToDirectory(); + if (directory == NULL) { + _NotifyEntryCreatedOrRemoved(entry, opcode); + return; + } + + for (EntryMap::Iterator it = directory->GetEntryIterator(); it.HasNext();) + _NotifyFilesCreatedOrRemoved(it.Next(), opcode); +} + + +void +PathHandler::_NotifyEntryCreatedOrRemoved(Entry* entry, int32 opcode) const +{ + Node* node = entry->Node(); + _NotifyEntryCreatedOrRemoved( + NotOwningEntryRef(entry->Parent()->NodeRef(), entry->Name()), + node->NodeRef(), _EntryPath(entry), node->IsDirectory(), opcode); +} + + +void +PathHandler::_NotifyEntryCreatedOrRemoved(const entry_ref& entryRef, + const node_ref& nodeRef, const char* path, bool isDirectory, int32 opcode) + const +{ + if (isDirectory ? _WatchFilesOnly() : _WatchDirectoriesOnly()) + return; + + TRACE("%p->PathHandler::_NotifyEntryCreatedOrRemoved(): entry %s: %" + B_PRIdDEV ":%" B_PRIdINO ":\"%s\", node: %" B_PRIdDEV ":%" B_PRIdINO + "\n", this, opcode == B_ENTRY_CREATED ? "created" : "removed", + entryRef.device, entryRef.directory, entryRef.name, nodeRef.device, + nodeRef.node); + + BMessage message(B_PATH_MONITOR); + message.AddInt32("opcode", opcode); + message.AddInt32("device", entryRef.device); + message.AddInt64("directory", entryRef.directory); + message.AddInt32("node device", nodeRef.device); + // This field is not in a usual node monitoring message, since the node + // the created/removed entry refers to always belongs to the same FS as + // the directory, as another FS cannot yet/no longer be mounted there. + // In our case, however, this can very well be the case, e.g. when the + // the notification is triggered in response to a directory tree having + // been moved into/out of our path. + message.AddInt64("node", nodeRef.node); + message.AddString("name", entryRef.name); + + _NotifyTarget(message, path); +} + + +void +PathHandler::_NotifyEntryMoved(const entry_ref& fromEntryRef, + const entry_ref& toEntryRef, const node_ref& nodeRef, const char* path, + bool isDirectory, bool wasAdded, bool wasRemoved) const +{ + if ((isDirectory && _WatchFilesOnly()) + || (!isDirectory && _WatchDirectoriesOnly())) { + return; + } + + TRACE("%p->PathHandler::_NotifyEntryMoved(): entry: %" B_PRIdDEV ":%" + B_PRIdINO ":\"%s\" -> %" B_PRIdDEV ":%" B_PRIdINO ":\"%s\", node: %" + B_PRIdDEV ":%" B_PRIdINO "\n", this, fromEntryRef.device, + fromEntryRef.directory, fromEntryRef.name, toEntryRef.device, + toEntryRef.directory, toEntryRef.name, nodeRef.device, nodeRef.node); + + BMessage message(B_PATH_MONITOR); + message.AddInt32("opcode", B_ENTRY_MOVED); + message.AddInt32("device", fromEntryRef.device); + message.AddInt64("from directory", fromEntryRef.directory); + message.AddInt64("to directory", toEntryRef.directory); + message.AddInt32("node device", nodeRef.device); + message.AddInt64("node", nodeRef.node); + message.AddString("from name", fromEntryRef.name); + message.AddString("name", toEntryRef.name); + + if (wasAdded) + message.AddBool("added", true); + if (wasRemoved) + message.AddBool("removed", true); + + _NotifyTarget(message, path); +} + + +void +PathHandler::_NotifyTarget(BMessage& message, const char* path) const +{ + message.what = B_PATH_MONITOR; + if (path != NULL && path[0] != '\0') + message.AddString("path", path); + message.AddString("watched_path", fPath.String()); + fTarget.SendMessage(&message); +} + + + +BString +PathHandler::_NodePath(const Node* node) const +{ + if (Entry* entry = node->FirstNodeEntry()) + return _EntryPath(entry); + return node == fBaseNode ? fPath : BString(); +} + + +BString +PathHandler::_EntryPath(const Entry* entry) const +{ + return make_path(_NodePath(entry->Parent()), entry->Name()); +} + + +bool +PathHandler::_WatchRecursively() const +{ + return (fFlags & B_WATCH_RECURSIVELY) != 0; +} + + +bool +PathHandler::_WatchFilesOnly() const +{ + return (fFlags & B_WATCH_FILES_ONLY) != 0; +} + + +bool +PathHandler::_WatchDirectoriesOnly() const +{ + return (fFlags & B_WATCH_DIRECTORIES_ONLY) != 0; +} + + +} // namespace + + +// #pragma mark - BPathMonitor + + +namespace BPrivate { BPathMonitor::BPathMonitor() @@ -978,6 +1968,136 @@ BPathMonitor::~BPathMonitor() } +/*static*/ status_t +BPathMonitor::StartWatching(const char* path, uint32 flags, + const BMessenger& target) +{ + TRACE("BPathMonitor::StartWatching(%s, %" B_PRIx32 ")\n", path, flags); + + if (path == NULL || path[0] == '\0') + return B_BAD_VALUE; + + // B_WATCH_FILES_ONLY and B_WATCH_DIRECTORIES_ONLY are mutual exclusive + if ((flags & B_WATCH_FILES_ONLY) != 0 + && (flags & B_WATCH_DIRECTORIES_ONLY) != 0) { + return B_BAD_VALUE; + } + + status_t status = _InitIfNeeded(); + if (status != B_OK) + return status; + + BAutolock _(sLocker); + + Watcher* watcher = sWatchers->Lookup(target); + bool newWatcher = false; + if (watcher != NULL) { + // If there's already a handler for the path, we'll replace it, but + // add its flags. + if (PathHandler* handler = watcher->Lookup(path)) { + // keep old flags save for conflicting mutually exclusive ones + uint32 oldFlags = handler->Flags(); + const uint32 kMutuallyExclusiveFlags + = B_WATCH_FILES_ONLY | B_WATCH_DIRECTORIES_ONLY; + if ((flags & kMutuallyExclusiveFlags) != 0) + oldFlags &= ~(uint32)kMutuallyExclusiveFlags; + flags |= oldFlags; + + watcher->Remove(handler); + handler->Quit(); + } + } else { + watcher = Watcher::Create(target); + if (watcher == NULL) + return B_NO_MEMORY; + sWatchers->Insert(watcher); + newWatcher = true; + } + + PathHandler* handler = new (std::nothrow) PathHandler(path, flags, target, + sLooper); + status = handler != NULL ? handler->InitCheck() : B_NO_MEMORY; + + if (status != B_OK) { + if (handler != NULL) + handler->Quit(); + + if (newWatcher) { + sWatchers->Remove(watcher); + delete watcher; + } + } + + watcher->Insert(handler); + return B_OK; +} + + +/*static*/ status_t +BPathMonitor::StopWatching(const char* path, const BMessenger& target) +{ + if (sLocker == NULL) + return B_BAD_VALUE; + + TRACE("BPathMonitor::StopWatching(%s)\n", path); + + BAutolock _(sLocker); + + Watcher* watcher = sWatchers->Lookup(target); + if (watcher == NULL) + return B_BAD_VALUE; + + PathHandler* handler = watcher->Lookup(path); + if (handler == NULL) + return B_BAD_VALUE; + + watcher->Remove(handler); + handler->Quit(); + + if (watcher->IsEmpty()) { + sWatchers->Remove(watcher); + delete watcher; + } + + return B_OK; +} + + +/*static*/ status_t +BPathMonitor::StopWatching(const BMessenger& target) +{ + if (sLocker == NULL) + return B_BAD_VALUE; + + BAutolock _(sLocker); + + Watcher* watcher = sWatchers->Lookup(target); + if (watcher == NULL) + return B_BAD_VALUE; + + // delete handlers + PathHandler* handler = watcher->Clear(true); + while (handler != NULL) { + PathHandler* nextHandler = handler->HashNext(); + handler->Quit(); + handler = nextHandler; + } + + sWatchers->Remove(watcher); + delete watcher; + + return B_OK; +} + + +/*static*/ void +BPathMonitor::SetWatchingInterface(BWatchingInterface* watchingInterface) +{ + sWatchingInterface = watchingInterface != NULL + ? watchingInterface : sDefaultWatchingInterface; +} + + /*static*/ status_t BPathMonitor::_InitIfNeeded() { @@ -989,7 +2109,7 @@ BPathMonitor::_InitIfNeeded() /*static*/ void BPathMonitor::_Init() { - sLocker = new (nothrow) BLocker("path monitor"); + sLocker = new (std::nothrow) BLocker("path monitor"); TRACE("Create PathMonitor locker\n"); if (sLocker == NULL) return; @@ -998,10 +2118,14 @@ BPathMonitor::_Init() if (sDefaultWatchingInterface == NULL) return; + sWatchers = new(std::nothrow) WatcherMap; + if (sWatchers == NULL || sWatchers->Init() != B_OK) + return; + if (sWatchingInterface == NULL) SetWatchingInterface(sDefaultWatchingInterface); - BLooper* looper = new (nothrow) BLooper("PathMonitor looper"); + BLooper* looper = new (std::nothrow) BLooper("PathMonitor looper"); TRACE("Start PathMonitor looper\n"); if (looper == NULL) return; @@ -1015,117 +2139,6 @@ BPathMonitor::_Init() } -/*static*/ status_t -BPathMonitor::StartWatching(const char* path, uint32 flags, - const BMessenger& target) -{ - TRACE("StartWatching(%s)\n", path); - - status_t status = _InitIfNeeded(); - if (status != B_OK) - return status; - - BAutolock _(sLocker); - - WatcherMap::iterator iterator = sWatchers.find(target); - Watcher* watcher = NULL; - if (iterator != sWatchers.end()) - watcher = iterator->second; - - PathHandler* handler = new (nothrow) PathHandler(path, flags, target, - sLooper); - if (handler == NULL) - return B_NO_MEMORY; - status = handler->InitCheck(); - if (status < B_OK) { - delete handler; - return status; - } - - if (watcher == NULL) { - watcher = new (nothrow) BPrivate::Watcher; - if (watcher == NULL) { - delete handler; - return B_NO_MEMORY; - } - sWatchers[target] = watcher; - } - - watcher->handlers[path] = handler; - return B_OK; -} - - -/*static*/ status_t -BPathMonitor::StopWatching(const char* path, const BMessenger& target) -{ - if (sLocker == NULL) - return B_NO_INIT; - - TRACE("StopWatching(%s)\n", path); - - BAutolock _(sLocker); - - WatcherMap::iterator iterator = sWatchers.find(target); - if (iterator == sWatchers.end()) - return B_BAD_VALUE; - - Watcher* watcher = iterator->second; - HandlerMap::iterator i = watcher->handlers.find(path); - - if (i == watcher->handlers.end()) - return B_BAD_VALUE; - - PathHandler* handler = i->second; - watcher->handlers.erase(i); - - handler->Quit(); - - if (watcher->handlers.empty()) { - sWatchers.erase(iterator); - delete watcher; - } - - return B_OK; -} - - -/*static*/ status_t -BPathMonitor::StopWatching(const BMessenger& target) -{ - if (sLocker == NULL) - return B_NO_INIT; - - BAutolock _(sLocker); - - WatcherMap::iterator iterator = sWatchers.find(target); - if (iterator == sWatchers.end()) - return B_BAD_VALUE; - - Watcher* watcher = iterator->second; - while (!watcher->handlers.empty()) { - HandlerMap::iterator i = watcher->handlers.begin(); - PathHandler* handler = i->second; - watcher->handlers.erase(i); - - handler->Quit(); - } - - sWatchers.erase(iterator); - delete watcher; - - return B_OK; -} - - -/*static*/ void -BPathMonitor::SetWatchingInterface(BWatchingInterface* watchingInterface) -{ - sWatchingInterface = watchingInterface != NULL - ? watchingInterface : sDefaultWatchingInterface; -} - - // #pragma mark - BWatchingInterface