예제 #1
0
            /// <summary>Removes the watched directory from our state, and optionally removes the inotify watch itself.</summary>
            /// <param name="directoryEntry">The directory entry to remove.</param>
            /// <param name="removeInotify">true to remove the inotify watch; otherwise, false.  The default is true.</param>
            private void RemoveWatchedDirectoryUnlocked(WatchedDirectory directoryEntry, bool removeInotify)
            {
                // If the directory has children, recursively remove them (see comments on recursion in AddDirectoryWatch).
                if (directoryEntry.Children != null)
                {
                    foreach (WatchedDirectory child in directoryEntry.Children)
                    {
                        RemoveWatchedDirectoryUnlocked(child, removeInotify);
                    }
                    directoryEntry.Children = null;
                }

                // Then remove the directory itself.
                _wdToPathMap.Remove(directoryEntry.WatchDescriptor);

                // And if the caller has requested, remove the associated inotify watch.
                if (removeInotify)
                {
                    SysCall(fd =>
                    {
                        // Remove the inotify watch.  This could fail if our state has become inconsistent
                        // with the state of the world (e.g. due to lost events).  So we don't want failures
                        // to throw exceptions, but we do assert to detect coding problems during debugging.
                        long result = Interop.libc.inotify_rm_watch(fd, directoryEntry.WatchDescriptor);
                        Debug.Assert(result >= 0);
                        return(0);
                    });
                }
            }
예제 #2
0
        private static async Task ExecuteSync(WatchedDirectory watchedDirectory, Stream stream)
        {
            while (true)
            {
                await Task.Delay(SyncIntervall);

                watchedDirectory.Sync(stream);
            }
        }
예제 #3
0
 /// <summary>Removes the watched directory from our state, and optionally removes the inotify watch itself.</summary>
 /// <param name="directoryEntry">The directory entry to remove.</param>
 /// <param name="removeInotify">true to remove the inotify watch; otherwise, false.  The default is true.</param>
 private void RemoveWatchedDirectory(WatchedDirectory directoryEntry, bool removeInotify = true)
 {
     Debug.Assert(_includeSubdirectories);
     lock (SyncObj)
     {
         if (directoryEntry.Parent != null)
         {
             directoryEntry.Parent.Children.Remove(directoryEntry);
         }
         RemoveWatchedDirectoryUnlocked(directoryEntry, removeInotify);
     }
 }
예제 #4
0
        private static async Task ExecuteWatchedDirectory(WatchedDirectory watchedDirectory, BlockingCollection <FileEvent> queue)
        {
            while (true)
            {
                if (queue.TryTake(out var fileEvent))
                {
                    watchedDirectory.Update(fileEvent);
                }

                await Task.Delay(PollingIntervallInMilliseconds);
            }
        }
예제 #5
0
        private static void Main(string[] args)
        {
            AddLogger();

            var queue            = new BlockingCollection <FileEvent>();
            var watchedDirectory = new WatchedDirectory();

            Task.Run(() => ExecuteDirectoryWatcher(queue));
            Task.Run(() => ExecuteWatchedDirectory(watchedDirectory, queue));
            Task.Run(() => ExecuteSync(watchedDirectory, Console.OpenStandardOutput()));

            Task.Delay(1000 * 60 * 60 * 24).Wait();
            Console.ReadLine();
        }
예제 #6
0
 public AddInWatcher(AddInReloaderConfiguration config)
 {
     foreach (var addIn in config.WatchedAddIns)
     {
         foreach (var file in addIn.WatchedFiles)
         {
             var directory = Path.GetDirectoryName(file.Path);
             WatchedDirectory wd;
             if (!_watchedDirectories.TryGetValue(directory, out wd))
             {
                 wd = new WatchedDirectory(directory, InvalidateAddIn);
             }
             wd.WatchAddIn(addIn);
         }
     }
 }
예제 #7
0
 public AddInWatcher(AddInReloaderConfiguration config)
 {
     foreach (var addIn in config.WatchedAddIns)
     {
         foreach (var file in addIn.WatchedFiles)
         {
             var directory = Path.GetDirectoryName(file.Path);
             WatchedDirectory wd;
             if (!_watchedDirectories.TryGetValue(directory, out wd))
             {
                 wd = new WatchedDirectory(directory, InvalidateAddIn);
             }
             wd.WatchAddIn(addIn);
         }
     }
 }
예제 #8
0
 /// <summary>Adds a watch on a directory to the existing inotify handle.</summary>
 /// <param name="parent">The parent directory entry.</param>
 /// <param name="directoryName">The new directory path to monitor, relative to the root.</param>
 private void AddDirectoryWatch(WatchedDirectory parent, string directoryName)
 {
     lock (SyncObj)
     {
         // The read syscall on the file descriptor will block until either close is called or until
         // all previously added watches are removed.  We don't want to rely on close, as a) that could
         // lead to race conditions where we inadvertently read from a recycled file descriptor, and b)
         // the SafeFileHandle that wraps the file descriptor can't be disposed (thus closing
         // the underlying file descriptor and allowing read to wake up) while there's an active ref count
         // against the handle, so we'd deadlock if we relied on that approach.  Instead, we want to follow
         // the approach of removing all watches when we're done, which means we also don't want to
         // add any new watches once the count hits zero.
         if (parent == null || _wdToPathMap.Count > 0)
         {
             Debug.Assert(parent != null || _wdToPathMap.Count == 0);
             AddDirectoryWatchUnlocked(parent, directoryName);
         }
     }
 }
예제 #9
0
 public WatchedDirectoryTests()
 {
     _sut = new WatchedDirectory();
 }
예제 #10
0
            /// <summary>Removes the watched directory from our state, and optionally removes the inotify watch itself.</summary>
            /// <param name="directoryEntry">The directory entry to remove.</param>
            /// <param name="removeInotify">true to remove the inotify watch; otherwise, false.  The default is true.</param>
            private void RemoveWatchedDirectoryUnlocked(WatchedDirectory directoryEntry, bool removeInotify)
            {
                // If the directory has children, recursively remove them (see comments on recursion in AddDirectoryWatch).
                if (directoryEntry.Children != null) 
                {
                    foreach (WatchedDirectory child in directoryEntry.Children) 
                    {
                        RemoveWatchedDirectoryUnlocked (child, removeInotify);
                    }
                    directoryEntry.Children = null;
                }

                // Then remove the directory itself.
                _wdToPathMap.Remove(directoryEntry.WatchDescriptor);

                // And if the caller has requested, remove the associated inotify watch.
                if (removeInotify)
                {
                    SysCall(fd => 
                    {
                        // Remove the inotify watch.  This could fail if our state has become inconsistent
                        // with the state of the world (e.g. due to lost events).  So we don't want failures
                        // to throw exceptions, but we do assert to detect coding problems during debugging.
                        long result = Interop.libc.inotify_rm_watch(fd, directoryEntry.WatchDescriptor);
                        Debug.Assert(result >= 0);
                        return 0;
                    });
                }
            }
예제 #11
0
 /// <summary>Removes the watched directory from our state, and optionally removes the inotify watch itself.</summary>
 /// <param name="directoryEntry">The directory entry to remove.</param>
 /// <param name="removeInotify">true to remove the inotify watch; otherwise, false.  The default is true.</param>
 private void RemoveWatchedDirectory(WatchedDirectory directoryEntry, bool removeInotify = true)
 {
     Debug.Assert (_includeSubdirectories);
     lock (SyncObj)
     {
         if (directoryEntry.Parent != null)
         {
             directoryEntry.Parent.Children.Remove (directoryEntry);
         }
         RemoveWatchedDirectoryUnlocked (directoryEntry, removeInotify);
     }
 }
예제 #12
0
            /// <summary>Adds a watch on a directory to the existing inotify handle.</summary>
            /// <param name="parent">The parent directory entry.</param>
            /// <param name="directoryName">The new directory path to monitor, relative to the root.</param>
            private WatchedDirectory AddDirectoryWatchUnlocked(WatchedDirectory parent, string directoryName)
            {
                string fullPath = parent != null ? parent.GetPath(false, directoryName) : directoryName;

                // Add a watch for the full path.  If the path is already being watched, this will return 
                // the existing descriptor.  This works even in the case of a rename.
                int wd = (int)SysCall(fd => Interop.libc.inotify_add_watch(fd, fullPath, (uint)_notifyFilters));

                // Then store the path information into our map.
                WatchedDirectory directoryEntry;
                bool isNewDirectory = false;
                if (_wdToPathMap.TryGetValue(wd, out directoryEntry))
                {
                    // The watch descriptor was already in the map.  Hard links on directories
                    // aren't possible, and symlinks aren't annotated as IN_ISDIR,
                    // so this is a rename. (In extremely remote cases, this could be
                    // a recycled watch descriptor if many, many events were lost
                    // such that our dictionary got very inconsistent with the state
                    // of the world, but there's little that can be done about that.)
                    if (directoryEntry.Parent != parent)
                    {
                        if (directoryEntry.Parent != null)
                        {
                            directoryEntry.Parent.Children.Remove (directoryEntry);
                        }
                        directoryEntry.Parent = parent;
                        if (parent != null)
                        {
                            parent.InitializedChildren.Add (directoryEntry);
                        }
                    }
                    directoryEntry.Name = directoryName;
                }
                else
                {
                    // The watch descriptor wasn't in the map.  This is a creation.
                    directoryEntry = new WatchedDirectory 
                    {
                        Parent = parent,
                        WatchDescriptor = wd,
                        Name = directoryName
                    };
                    if (parent != null)
                    {
                        parent.InitializedChildren.Add (directoryEntry);
                    }
                    _wdToPathMap.Add(wd, directoryEntry);
                    isNewDirectory = true;
                }

                // Since inotify doesn't handle nesting implicitly, explicitly
                // add a watch for each child directory if the developer has
                // asked for subdirectories to be included.
                if (isNewDirectory && _includeSubdirectories)
                {
                    // This method is recursive.  If we expect to see hierarchies
                    // so deep that it would cause us to overflow the stack, we could
                    // consider using an explicit stack object rather than recursion.
                    // This is unlikely, however, given typical directory names
                    // and max path limits.
                    foreach (string subDir in Directory.EnumerateDirectories(fullPath))
                    {
                        AddDirectoryWatchUnlocked(directoryEntry, System.IO.Path.GetFileName(subDir));
                        // AddDirectoryWatchUnlocked will add the new directory to 
                        // this.Children, so we don't have to / shouldn't also do it here.
                    }
                }

                return directoryEntry;
            }
예제 #13
0
 /// <summary>Adds a watch on a directory to the existing inotify handle.</summary>
 /// <param name="parent">The parent directory entry.</param>
 /// <param name="directoryName">The new directory path to monitor, relative to the root.</param>
 private void AddDirectoryWatch(WatchedDirectory parent, string directoryName)
 {
     lock (SyncObj)
     {
         // The read syscall on the file descriptor will block until either close is called or until
         // all previously added watches are removed.  We don't want to rely on close, as a) that could
         // lead to race conditions where we inadvertently read from a recycled file descriptor, and b)
         // the SafeFileHandle that wraps the file descriptor can't be disposed (thus closing
         // the underlying file descriptor and allowing read to wake up) while there's an active ref count
         // against the handle, so we'd deadlock if we relied on that approach.  Instead, we want to follow
         // the approach of removing all watches when we're done, which means we also don't want to 
         // add any new watches once the count hits zero.
         if (parent == null || _wdToPathMap.Count > 0)
         {
             Debug.Assert(parent != null || _wdToPathMap.Count == 0);
             AddDirectoryWatchUnlocked(parent, directoryName);
         }
     }
 }
            /// <summary>Adds a watch on a directory to the existing inotify handle.</summary>
            /// <param name="parent">The parent directory entry.</param>
            /// <param name="directoryName">The new directory path to monitor, relative to the root.</param>
            private void AddDirectoryWatchUnlocked(WatchedDirectory parent, string directoryName)
            {
                string fullPath = parent != null ? parent.GetPath(false, directoryName) : directoryName;

                // inotify_add_watch will fail if this is a symlink, so check that we didn't get a symlink
                Interop.Sys.FileStatus status = default(Interop.Sys.FileStatus);
                if ((Interop.Sys.LStat(fullPath, out status) == 0) &&
                    ((status.Mode & (uint)Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK))
                {
                    return;
                }

                // Add a watch for the full path.  If the path is already being watched, this will return 
                // the existing descriptor.  This works even in the case of a rename. We also add the DONT_FOLLOW
                // and EXCL_UNLINK flags to keep parity with Windows where we don't pickup symlinks or unlinked
                // files (which don't exist in Windows)
                int wd = Interop.Sys.INotifyAddWatch(_inotifyHandle, fullPath, (uint)(this._notifyFilters | Interop.Sys.NotifyEvents.IN_DONT_FOLLOW | Interop.Sys.NotifyEvents.IN_EXCL_UNLINK));
                if (wd == -1)
                {
                    // If we get an error when trying to add the watch, don't let that tear down processing.  Instead,
                    // raise the Error event with the exception and let the user decide how to handle it.

                    Interop.ErrorInfo error = Interop.Sys.GetLastErrorInfo();
                    Exception exc;
                    if (error.Error == Interop.Error.ENOSPC)
                    {
                        string maxValue = ReadMaxUserLimit(MaxUserWatchesPath);
                        string message = !string.IsNullOrEmpty(maxValue) ?
                            SR.Format(SR.IOException_INotifyWatchesUserLimitExceeded_Value, maxValue) :
                            SR.IOException_INotifyWatchesUserLimitExceeded;
                        exc = new IOException(message, error.RawErrno);
                    }
                    else
                    {
                        exc = Interop.GetExceptionForIoErrno(error, fullPath);
                    }

                    FileSystemWatcher watcher;
                    if (_weakWatcher.TryGetTarget(out watcher))
                    {
                        watcher.OnError(new ErrorEventArgs(exc));
                    }

                    return;
                }

                // Then store the path information into our map.
                WatchedDirectory directoryEntry;
                bool isNewDirectory = false;
                if (_wdToPathMap.TryGetValue(wd, out directoryEntry))
                {
                    // The watch descriptor was already in the map.  Hard links on directories
                    // aren't possible, and symlinks aren't annotated as IN_ISDIR,
                    // so this is a rename. (In extremely remote cases, this could be
                    // a recycled watch descriptor if many, many events were lost
                    // such that our dictionary got very inconsistent with the state
                    // of the world, but there's little that can be done about that.)
                    if (directoryEntry.Parent != parent)
                    {
                        if (directoryEntry.Parent != null)
                        {
                            directoryEntry.Parent.Children.Remove (directoryEntry);
                        }
                        directoryEntry.Parent = parent;
                        if (parent != null)
                        {
                            parent.InitializedChildren.Add (directoryEntry);
                        }
                    }
                    directoryEntry.Name = directoryName;
                }
                else
                {
                    // The watch descriptor wasn't in the map.  This is a creation.
                    directoryEntry = new WatchedDirectory 
                    {
                        Parent = parent,
                        WatchDescriptor = wd,
                        Name = directoryName
                    };
                    if (parent != null)
                    {
                        parent.InitializedChildren.Add (directoryEntry);
                    }
                    _wdToPathMap.Add(wd, directoryEntry);
                    isNewDirectory = true;
                }

                // Since inotify doesn't handle nesting implicitly, explicitly
                // add a watch for each child directory if the developer has
                // asked for subdirectories to be included.
                if (isNewDirectory && _includeSubdirectories)
                {
                    // This method is recursive.  If we expect to see hierarchies
                    // so deep that it would cause us to overflow the stack, we could
                    // consider using an explicit stack object rather than recursion.
                    // This is unlikely, however, given typical directory names
                    // and max path limits.
                    foreach (string subDir in Directory.EnumerateDirectories(fullPath))
                    {
                        AddDirectoryWatchUnlocked(directoryEntry, System.IO.Path.GetFileName(subDir));
                        // AddDirectoryWatchUnlocked will add the new directory to 
                        // this.Children, so we don't have to / shouldn't also do it here.
                    }
                }
            }
예제 #15
0
            /// <summary>
            /// Main processing loop.  This is currently implemented as a synchronous operation that continually
            /// reads events and processes them... in the future, this could be changed to use asynchronous processing
            /// if the impact of using a thread-per-FileSystemWatcher is too high.
            /// </summary>
            private void ProcessEvents()
            {
                // When cancellation is requested, clear out all watches.  This should force any active or future reads
                // on the inotify handle to return 0 bytes read immediately, allowing us to wake up from the blocking call
                // and exit the processing loop and clean up.
                var ctr = _cancellationToken.Register(obj => ((RunningInstance)obj).CancellationCallback(), this);

                try
                {
                    // Previous event information
                    string           previousEventName   = null;
                    WatchedDirectory previousEventParent = null;
                    uint             previousEventCookie = 0;

                    // Process events as long as we're not canceled and there are more to read...
                    NotifyEvent nextEvent;
                    while (!_cancellationToken.IsCancellationRequested && TryReadEvent(out nextEvent))
                    {
                        // Try to get the actual watcher from our weak reference.  We maintain a weak reference most of the time
                        // so as to avoid a rooted cycle that would prevent our processing loop from ever ending
                        // if the watcher is dropped by the user without being disposed. If we can't get the watcher,
                        // there's nothing more to do (we can't raise events), so bail.
                        FileSystemWatcher watcher;
                        if (!_weakWatcher.TryGetTarget(out watcher))
                        {
                            break;
                        }

                        uint             mask                     = nextEvent.mask;
                        bool             addWatch                 = false;
                        string           expandedName             = null;
                        WatchedDirectory associatedDirectoryEntry = null;

                        if (nextEvent.wd != -1) // wd is -1 for events like IN_Q_OVERFLOW that aren't tied to a particular watch descriptor
                        {
                            // Look up the directory information for the supplied wd
                            lock (SyncObj)
                            {
                                if (!_wdToPathMap.TryGetValue(nextEvent.wd, out associatedDirectoryEntry))
                                {
                                    // The watch descriptor could be missing from our dictionary if it was removed
                                    // due to cancellation, or if we already removed it and this is a related event
                                    // like IN_IGNORED.  In any case, just ignore it... even if for some reason we
                                    // should have the value, there's little we can do about it at this point,
                                    // and there's no more processing of this event we can do without it.
                                    continue;
                                }
                            }
                            expandedName = associatedDirectoryEntry.GetPath(true, nextEvent.name);
                        }

                        // Determine whether the affected object is a directory (rather than a file).
                        // If it is, we may need to do special processing, such as adding a watch for new
                        // directories if IncludeSubdirectories is enabled.  Since we're only watching
                        // directories, any IN_IGNORED event is also for a directory.
                        bool isDir = (mask & (uint)(Interop.libc.NotifyEvents.IN_ISDIR | Interop.libc.NotifyEvents.IN_IGNORED)) != 0;

                        // Renames come in the form of two events: IN_MOVED_FROM and IN_MOVED_TO.
                        // In general, these should come as a sequence, one immediately after the other.
                        // So, we delay raising an event for IN_MOVED_FROM until we see what comes next.
                        if (previousEventName != null && ((mask & (uint)Interop.libc.NotifyEvents.IN_MOVED_TO) == 0 || previousEventCookie != nextEvent.cookie))
                        {
                            // IN_MOVED_FROM without an immediately-following corresponding IN_MOVED_TO.
                            // We have to assume that it was moved outside of our root watch path, which
                            // should be considered a deletion to match Win32 behavior.
                            // But since we explicitly added watches on directories, if it's a directory it'll
                            // still be watched, so we need to explicitly remove the watch.
                            if (previousEventParent != null && previousEventParent.Children != null)
                            {
                                // previousEventParent will be non-null iff the IN_MOVED_FROM
                                // was for a directory, in which case previousEventParent is that directory's
                                // parent and previousEventName is the name of the directory to be removed.
                                foreach (WatchedDirectory child in previousEventParent.Children)
                                {
                                    if (child.Name == previousEventName)
                                    {
                                        RemoveWatchedDirectory(child);
                                        break;
                                    }
                                }
                            }

                            // Then fire the deletion event, even though the event was IN_MOVED_FROM.
                            watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Deleted, previousEventName);

                            previousEventName   = null;
                            previousEventParent = null;
                            previousEventCookie = 0;
                        }

                        const Interop.libc.NotifyEvents switchMask =
                            Interop.libc.NotifyEvents.IN_Q_OVERFLOW | Interop.libc.NotifyEvents.IN_IGNORED |
                            Interop.libc.NotifyEvents.IN_CREATE | Interop.libc.NotifyEvents.IN_DELETE |
                            Interop.libc.NotifyEvents.IN_ACCESS | Interop.libc.NotifyEvents.IN_MODIFY | Interop.libc.NotifyEvents.IN_ATTRIB |
                            Interop.libc.NotifyEvents.IN_MOVED_FROM | Interop.libc.NotifyEvents.IN_MOVED_TO;
                        switch ((Interop.libc.NotifyEvents)(mask & (uint)switchMask))
                        {
                        case Interop.libc.NotifyEvents.IN_Q_OVERFLOW:
                            watcher.NotifyInternalBufferOverflowEvent();
                            break;

                        case Interop.libc.NotifyEvents.IN_CREATE:
                            watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Created, expandedName);
                            addWatch = true;
                            break;

                        case Interop.libc.NotifyEvents.IN_IGNORED:
                            // We're getting an IN_IGNORED because a directory watch was removed.
                            // and we're getting this far in our code because we still have an entry for it
                            // in our dictionary.  So we want to clean up the relevant state, but not clean
                            // attempt to call back to inotify to remove the watches.
                            RemoveWatchedDirectory(associatedDirectoryEntry, removeInotify: false);
                            break;

                        case Interop.libc.NotifyEvents.IN_DELETE:
                            watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Deleted, expandedName);
                            // We don't explicitly RemoveWatchedDirectory here, as that'll be handled
                            // by IN_IGNORED processing if this is a directory.
                            break;

                        case Interop.libc.NotifyEvents.IN_ACCESS:
                        case Interop.libc.NotifyEvents.IN_MODIFY:
                        case Interop.libc.NotifyEvents.IN_ATTRIB:
                            watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Changed, expandedName);
                            break;

                        case Interop.libc.NotifyEvents.IN_MOVED_FROM:
                            previousEventName   = expandedName;
                            previousEventParent = isDir ? associatedDirectoryEntry : null;
                            previousEventCookie = nextEvent.cookie;
                            break;

                        case Interop.libc.NotifyEvents.IN_MOVED_TO:
                            if (previousEventName != null)
                            {
                                // If the previous name from IN_MOVED_FROM is non-null, then this is a rename.
                                watcher.NotifyRenameEventArgs(WatcherChangeTypes.Renamed, expandedName, previousEventName);
                            }
                            else
                            {
                                // If it is null, then we didn't get an IN_MOVED_FROM (or we got it a long time
                                // ago and treated it as a deletion), in which case this is considered a creation.
                                watcher.NotifyFileSystemEventArgs(WatcherChangeTypes.Created, expandedName);
                            }
                            previousEventName   = null;
                            previousEventParent = null;
                            previousEventCookie = 0;
                            addWatch            = true; // for either rename or creation, we need to update our state
                            break;
                        }

                        // If the event signaled that there's a new subdirectory and if we're monitoring subdirectories,
                        // add a watch for it.
                        if (addWatch && isDir && _includeSubdirectories)
                        {
                            AddDirectoryWatch(associatedDirectoryEntry, nextEvent.name);
                        }

                        // Drop our strong reference to the watcher now that we're potentially going to block again for another read
                        watcher = null;
                    }
                }
                catch (Exception exc)
                {
                    FileSystemWatcher watcher;
                    if (_weakWatcher.TryGetTarget(out watcher))
                    {
                        watcher.OnError(new ErrorEventArgs(exc));
                    }
                }
                finally
                {
                    ctr.Dispose();
                    _inotifyHandle.Dispose();
                }
            }
예제 #16
0
            /// <summary>Adds a watch on a directory to the existing inotify handle.</summary>
            /// <param name="parent">The parent directory entry.</param>
            /// <param name="directoryName">The new directory path to monitor, relative to the root.</param>
            private WatchedDirectory AddDirectoryWatchUnlocked(WatchedDirectory parent, string directoryName)
            {
                string fullPath = parent != null?parent.GetPath(false, directoryName) : directoryName;

                // Add a watch for the full path.  If the path is already being watched, this will return
                // the existing descriptor.  This works even in the case of a rename.
                int wd = (int)SysCall(fd => Interop.libc.inotify_add_watch(fd, fullPath, (uint)_notifyFilters));

                // Then store the path information into our map.
                WatchedDirectory directoryEntry;
                bool             isNewDirectory = false;

                if (_wdToPathMap.TryGetValue(wd, out directoryEntry))
                {
                    // The watch descriptor was already in the map.  Hard links on directories
                    // aren't possible, and symlinks aren't annotated as IN_ISDIR,
                    // so this is a rename. (In extremely remote cases, this could be
                    // a recycled watch descriptor if many, many events were lost
                    // such that our dictionary got very inconsistent with the state
                    // of the world, but there's little that can be done about that.)
                    if (directoryEntry.Parent != parent)
                    {
                        if (directoryEntry.Parent != null)
                        {
                            directoryEntry.Parent.Children.Remove(directoryEntry);
                        }
                        directoryEntry.Parent = parent;
                        if (parent != null)
                        {
                            parent.InitializedChildren.Add(directoryEntry);
                        }
                    }
                    directoryEntry.Name = directoryName;
                }
                else
                {
                    // The watch descriptor wasn't in the map.  This is a creation.
                    directoryEntry = new WatchedDirectory
                    {
                        Parent          = parent,
                        WatchDescriptor = wd,
                        Name            = directoryName
                    };
                    if (parent != null)
                    {
                        parent.InitializedChildren.Add(directoryEntry);
                    }
                    _wdToPathMap.Add(wd, directoryEntry);
                    isNewDirectory = true;
                }

                // Since inotify doesn't handle nesting implicitly, explicitly
                // add a watch for each child directory if the developer has
                // asked for subdirectories to be included.
                if (isNewDirectory && _includeSubdirectories)
                {
                    // This method is recursive.  If we expect to see hierarchies
                    // so deep that it would cause us to overflow the stack, we could
                    // consider using an explicit stack object rather than recursion.
                    // This is unlikely, however, given typical directory names
                    // and max path limits.
                    foreach (string subDir in Directory.EnumerateDirectories(fullPath))
                    {
                        AddDirectoryWatchUnlocked(directoryEntry, System.IO.Path.GetFileName(subDir));
                        // AddDirectoryWatchUnlocked will add the new directory to
                        // this.Children, so we don't have to / shouldn't also do it here.
                    }
                }

                return(directoryEntry);
            }