Esempio n. 1
0
            private const int NAME_MAX = 255; // from limits.h

            /// <summary>Initializes the instance with all state necessary to operate a watch.</summary>
            internal RunningInstance(
                FileSystemWatcher watcher, SafeFileHandle inotifyHandle, string directoryPath,
                bool includeSubdirectories, Interop.libc.NotifyEvents notifyFilters, CancellationToken cancellationToken)
            {
                Debug.Assert(watcher != null);
                Debug.Assert(inotifyHandle != null && !inotifyHandle.IsInvalid && !inotifyHandle.IsClosed);
                Debug.Assert(directoryPath != null);

                _weakWatcher   = new WeakReference <FileSystemWatcher>(watcher);
                _inotifyHandle = inotifyHandle;
                _directoryPath = directoryPath;
                _buffer        = watcher.AllocateBuffer();
                Debug.Assert(_buffer != null && _buffer.Length > (c_INotifyEventSize + NAME_MAX + 1));
                _includeSubdirectories          = includeSubdirectories;
                _notifyFilters                  = notifyFilters;
                _cancellationToken              = cancellationToken;
                FileSystemWatcher.CaseSensitive = true;

                // Add a watch for this starting directory.  We keep track of the watch descriptor => directory information
                // mapping in a dictionary; this is needed in order to be able to determine the containing directory
                // for all notifications so that we can reconstruct the full path.
                AddDirectoryWatchUnlocked(null, directoryPath);

                // Schedule a task to read from the inotify queue and process the events.
                Task.Factory.StartNew(obj => ((RunningInstance)obj).ProcessEvents(),
                                      this, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);

                // PERF: As needed, we can look into making this use async I/O rather than burning
                // a thread that blocks in the read syscall.
            }
            private const int NAME_MAX = 255; // from limits.h

            /// <summary>Initializes the instance with all state necessary to operate a watch.</summary>
            internal RunningInstance(
                FileSystemWatcher watcher, SafeFileHandle inotifyHandle, string directoryPath,
                bool includeSubdirectories, Interop.libc.NotifyEvents notifyFilters, CancellationToken cancellationToken)
            {
                Debug.Assert(watcher != null);
                Debug.Assert(inotifyHandle != null && !inotifyHandle.IsInvalid && !inotifyHandle.IsClosed);
                Debug.Assert(directoryPath != null);

                _weakWatcher = new WeakReference<FileSystemWatcher>(watcher);
                _inotifyHandle = inotifyHandle;
                _directoryPath = directoryPath;
                _buffer = watcher.AllocateBuffer();
                Debug.Assert(_buffer != null && _buffer.Length > (c_INotifyEventSize + NAME_MAX + 1));
                _includeSubdirectories = includeSubdirectories;
                _notifyFilters = notifyFilters;
                _cancellationToken = cancellationToken;
                FileSystemWatcher.CaseSensitive = true;

                // Add a watch for this starting directory.  We keep track of the watch descriptor => directory information
                // mapping in a dictionary; this is needed in order to be able to determine the containing directory
                // for all notifications so that we can reconstruct the full path.
                AddDirectoryWatchUnlocked(null, directoryPath);

                // Schedule a task to read from the inotify queue and process the events.
                Task.Factory.StartNew(obj => ((RunningInstance)obj).ProcessEvents(),
                    this, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);

                // PERF: As needed, we can look into making this use async I/O rather than burning
                // a thread that blocks in the read syscall.
            }
Esempio n. 3
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();
                }
            }
Esempio n. 4
0
        /// <summary>
        /// Maps the FileSystemWatcher's NotifyFilters enumeration to the
        /// corresponding Interop.libc.NotifyEvents values.
        /// </summary>
        /// <param name="filters">The filters provided the by user.</param>
        /// <returns>The corresponding NotifyEvents values to use with inotify.</returns>
        private static Interop.libc.NotifyEvents TranslateFilters(NotifyFilters filters)
        {
            Interop.libc.NotifyEvents result = 0;

            // We always include a few special inotify watch values that configure
            // the watch's behavior.
            result |=
                Interop.libc.NotifyEvents.IN_ONLYDIR |     // we only allow watches on directories
                Interop.libc.NotifyEvents.IN_EXCL_UNLINK;  // we want to stop monitoring unlinked files

            // For the Created and Deleted events, we need to always
            // register for the created/deleted inotify events, regardless
            // of the supplied filters values. We explicitly don't include IN_DELETE_SELF.
            // The Windows implementation doesn't include notifications for the root directory,
            // and having this for subdirectories results in duplicate notifications, one from
            // the parent and one from self.
            result |=
                Interop.libc.NotifyEvents.IN_CREATE |
                Interop.libc.NotifyEvents.IN_DELETE;

            // For the Changed event, which inotify events we subscribe to
            // are based on the NotifyFilters supplied.
            const NotifyFilters filtersForAccess =
                NotifyFilters.LastAccess;
            const NotifyFilters filtersForModify =
                NotifyFilters.LastAccess |
                NotifyFilters.LastWrite |
                NotifyFilters.Security |
                NotifyFilters.Size;
            const NotifyFilters filtersForAttrib =
                NotifyFilters.Attributes |
                NotifyFilters.CreationTime |
                NotifyFilters.LastAccess |
                NotifyFilters.LastWrite |
                NotifyFilters.Security |
                NotifyFilters.Size;

            if ((filters & filtersForAccess) != 0)
            {
                result |= Interop.libc.NotifyEvents.IN_ACCESS;
            }
            if ((filters & filtersForModify) != 0)
            {
                result |= Interop.libc.NotifyEvents.IN_MODIFY;
            }
            if ((filters & filtersForAttrib) != 0)
            {
                result |= Interop.libc.NotifyEvents.IN_ATTRIB;
            }

            // For the Rename event, we'll register for the corresponding move inotify events if the
            // caller's NotifyFilters asks for notications related to names.
            const NotifyFilters filtersForMoved =
                NotifyFilters.FileName |
                NotifyFilters.DirectoryName;

            if ((filters & filtersForMoved) != 0)
            {
                result |=
                    Interop.libc.NotifyEvents.IN_MOVED_FROM |
                    Interop.libc.NotifyEvents.IN_MOVED_TO;
            }

            return(result);
        }