root/src/kits/storage/PathMonitor.cpp
/*
 * 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, superstippi@gmx.de
 *              Ingo Weinhold, ingo_weinhold@gmx.de
 */


#include <PathMonitor.h>

#include <pthread.h>
#include <stdio.h>

#include <Autolock.h>
#include <Directory.h>
#include <Entry.h>
#include <Handler.h>
#include <Locker.h>
#include <Looper.h>
#include <Path.h>
#include <String.h>

#include <AutoDeleter.h>
#include <NotOwningEntryRef.h>
#include <ObjectList.h>
#include <util/OpenHashTable.h>
#include <util/SinglyLinkedList.h>


#undef TRACE
//#define TRACE_PATH_MONITOR
#ifdef TRACE_PATH_MONITOR
#       define TRACE(...) debug_printf("BPathMonitor: " __VA_ARGS__)
#else
#       define TRACE(...) ;
#endif


// TODO: Support symlink components in the path.
// TODO: Support mounting/unmounting of volumes in path components and within
// the watched path tree.


#define WATCH_NODE_FLAG_MASK    0x00ff


namespace {


class Directory;
class Node;
struct WatcherHashDefinition;
typedef BOpenHashTable<WatcherHashDefinition> WatcherMap;


static pthread_once_t sInitOnce = PTHREAD_ONCE_INIT;
static WatcherMap* sWatchers = NULL;
static BLooper* sLooper = NULL;
static BPathMonitor::BWatchingInterface* sDefaultWatchingInterface = NULL;
static BPathMonitor::BWatchingInterface* sWatchingInterface = NULL;


//      #pragma mark -


/*! 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)
{
        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<AncestorHashDefinition> AncestorMap;


//      #pragma mark - Entry


class Entry : public SinglyLinkedListLinkImpl<Entry> {
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<Entry> 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<EntryHashDefinition> 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<NodeHashDefinition> 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* fromPath, 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<PathHandlerHashDefinition> 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;
        }

        // 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;

        // add ourselves to the looper
        looper->AddHandler(this);

        // 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;
        }
}


status_t
PathHandler::InitCheck() const
{
        return fStatus;
}


void
PathHandler::Quit()
{
        TRACE("%p->PathHandler::Quit()\n", this);
        sWatchingInterface->StopWatching(this);
        sLooper->RemoveHandler(this);
        delete this;
}


void
PathHandler::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case B_NODE_MONITOR:
                {
                        int32 opcode;
                        if (message->FindInt32("opcode", &opcode) != B_OK)
                                return;

                        switch (opcode) {
                                case B_ENTRY_CREATED:
                                        _EntryCreated(message);
                                        break;

                                case B_ENTRY_REMOVED:
                                        _EntryRemoved(message);
                                        break;

                                case B_ENTRY_MOVED:
                                        _EntryMoved(message);
                                        break;

                                default:
                                        _UnsetDuplicateEntryNotification();
                                        _NodeChanged(message);
                                        break;
                        }

                        break;
                }

                default:
                        BHandler::MessageReceived(message);
                        break;
        }
}


status_t
PathHandler::_CreateAncestors()
{
        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;
        struct stat st;
        if (entry.SetTo(&entryRef) != B_OK || entry.GetStat(&st) != B_OK
                || nodeRef != node_ref(st.st_dev, st.st_ino)) {
                return;
        }

        _EntryCreated(entryRef, nodeRef, S_ISDIR(st.st_mode), false, true, NULL);
}


void
PathHandler::_EntryRemoved(BMessage* message)
{
        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_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 fromPath;
                                        if (fromDirectoryNode != NULL) {
                                                fromPath = make_path(_NodePath(fromDirectoryNode),
                                                        fromEntryRef.name);
                                        }

                                        BString path;
                                        if (toDirectoryNode != NULL) {
                                                path = make_path(_NodePath(toDirectoryNode),
                                                        toEntryRef.name);
                                        }

                                        _NotifyEntryMoved(fromEntryRef, toEntryRef, nodeRef,
                                                fromPath, 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);
                                }
                        }

                        return;
                }

                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 {
                // 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 fromPath;
                                if (fromAncestor == fBaseAncestor)
                                        fromPath = make_path(fPath, fromEntryRef.name);

                                BString path;
                                if (toAncestor == fBaseAncestor)
                                        path = make_path(fPath, toEntryRef.name);

                                _NotifyEntryMoved(fromEntryRef, toEntryRef, nodeRef,
                                        fromPath, path, isDirectory, fromAncestor == NULL,
                                        toAncestor == NULL);
                        }
                        return;
                }
        }

        if (fromAncestor == NULL && toAncestor == NULL)
                return;

        if (fromAncestor == NULL) {
                _EntryCreated(toEntryRef, nodeRef, isDirectory, false, true, NULL);
                return;
        }

        if (toAncestor == NULL) {
                _EntryRemoved(fromEntryRef, nodeRef, false, true, NULL);
                return;
        }

        // 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 {
                        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,
                                fromIsBase ? fPath.String() : NULL,
                                toIsBase ? fPath.String() : NULL,
                                isDirectory, toIsBase, fromIsBase);
                }
                return;
        }

        _EntryRemoved(fromEntryRef, nodeRef, false, true, NULL);
        _EntryCreated(toEntryRef, nodeRef, isDirectory, false, true, NULL);
}


void
PathHandler::_NodeChanged(BMessage* message)
{
        node_ref 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);
}


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 = directoryNode->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 != NULL && 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<Node> 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* fromPath,
        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);

        if (fromPath != NULL && fromPath[0] != '\0')
                message.AddString("from path", fromPath);

        _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()
{
}


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 _(sLooper);

        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;
                }
                return status;
        }

        watcher->Insert(handler);
        return B_OK;
}


/*static*/ status_t
BPathMonitor::StopWatching(const char* path, const BMessenger& target)
{
        if (sLooper == NULL)
                return B_BAD_VALUE;

        TRACE("BPathMonitor::StopWatching(%s)\n", path);

        BAutolock _(sLooper);

        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 (sLooper == NULL)
                return B_BAD_VALUE;

        BAutolock _(sLooper);

        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()
{
        pthread_once(&sInitOnce, &BPathMonitor::_Init);
        return sLooper != NULL ? B_OK : B_NO_MEMORY;
}


/*static*/ void
BPathMonitor::_Init()
{
        sDefaultWatchingInterface = new(std::nothrow) BWatchingInterface;
        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 (std::nothrow) BLooper("PathMonitor looper");
        TRACE("Start PathMonitor looper\n");
        if (looper == NULL)
                return;
        thread_id thread = looper->Run();
        if (thread < 0) {
                delete looper;
                return;
        }

        sLooper = looper;
}


// #pragma mark - BWatchingInterface


BPathMonitor::BWatchingInterface::BWatchingInterface()
{
}


BPathMonitor::BWatchingInterface::~BWatchingInterface()
{
}


status_t
BPathMonitor::BWatchingInterface::WatchNode(const node_ref* node, uint32 flags,
        const BMessenger& target)
{
        return watch_node(node, flags, target);
}


status_t
BPathMonitor::BWatchingInterface::WatchNode(const node_ref* node, uint32 flags,
        const BHandler* handler, const BLooper* looper)
{
        return watch_node(node, flags, handler, looper);
}


status_t
BPathMonitor::BWatchingInterface::StopWatching(const BMessenger& target)
{
        return stop_watching(target);
}


status_t
BPathMonitor::BWatchingInterface::StopWatching(const BHandler* handler,
        const BLooper* looper)
{
        return stop_watching(handler, looper);
}


}       // namespace BPrivate