[System.IO] Phase 2 of revamping KeventWatcher (FileSystemWatcher implementation...
authorAlexis Christoforides <alexis@thenull.net>
Thu, 9 Oct 2014 05:16:40 +0000 (22:16 -0700)
committerAlexis Christoforides <alexis@thenull.net>
Tue, 28 Oct 2014 01:20:16 +0000 (21:20 -0400)
FileSystemWatcher.cs: Fixed multicast events.
SearchPattern.cs :  Fixed issue where case insensitivity was not being honored.
KeventWatcher.cs:
* Fixed several race conditions
* Fixed file descriptor duplication & leaks
* thread-safe/cleaner state transitions for watcher, and much cleaner stopping
* Now uses fsw.Filter parameter and respects IncludeSubdirectories = false. Use fsw.Filter to watch very large trees
* Added more error handling + OnError event
* Lots of behavior/reliability fixes and optimizations for Monitor() loop
* Fixed kevent/timespec struct definitions (for 64-bit build)

mcs/class/System/System.IO/FileSystemWatcher.cs
mcs/class/System/System.IO/KeventWatcher.cs
mcs/class/System/System.IO/SearchPattern.cs

index 125f07805f9403f212fe5789357ff92bd0d02f33..0d293e53188b4f6155380ba838edf3443f1f7117 100644 (file)
@@ -183,7 +183,10 @@ namespace System.IO {
                internal SearchPattern2 Pattern {
                        get {
                                if (pattern == null) {
-                                       pattern = new SearchPattern2 (MangledFilter);
+                                       if (watcher.GetType () == typeof (KeventWatcher))
+                                               pattern = new SearchPattern2 (MangledFilter, true); //assume we want to ignore case (OS X)
+                                       else
+                                               pattern = new SearchPattern2 (MangledFilter);
                                }
                                return pattern;
                        }
@@ -372,52 +375,60 @@ namespace System.IO {
                        ErrorEvent,
                        RenameEvent
                }
-               private void RaiseEvent (Delegate ev, EventArgs arg, EventType evtype)
-               {
-                       if (ev == null)
-                               return;
-
-                       if (synchronizingObject == null) {
-                               switch (evtype) {
-                               case EventType.RenameEvent:
-                                       ((RenamedEventHandler)ev).BeginInvoke (this, (RenamedEventArgs) arg, null, null);
-                                       break;
-                               case EventType.ErrorEvent:
-                                       ((ErrorEventHandler)ev).BeginInvoke (this, (ErrorEventArgs) arg, null, null);
-                                       break;
-                               case EventType.FileSystemEvent:
-                                       ((FileSystemEventHandler)ev).BeginInvoke (this, (FileSystemEventArgs) arg, null, null);
-                                       break;
-                               }
-                               return;
-                       }
                        
-                       synchronizingObject.BeginInvoke (ev, new object [] {this, arg});
-               }
-
                protected void OnChanged (FileSystemEventArgs e)
                {
-                       RaiseEvent (Changed, e, EventType.FileSystemEvent);
+                       if (Changed == null)
+                               return;
+
+                       if (synchronizingObject == null)
+                               Changed (this, e);
+                       else
+                               synchronizingObject.BeginInvoke (Changed, new object[] { this, e });
                }
 
                protected void OnCreated (FileSystemEventArgs e)
                {
-                       RaiseEvent (Created, e, EventType.FileSystemEvent);
+                       if (Created == null)
+                               return;
+
+                       if (synchronizingObject == null)
+                               Created (this, e);
+                       else
+                               synchronizingObject.BeginInvoke (Created, new object[] { this, e });
                }
 
                protected void OnDeleted (FileSystemEventArgs e)
                {
-                       RaiseEvent (Deleted, e, EventType.FileSystemEvent);
+                       if (Deleted == null)
+                               return;
+
+                       if (synchronizingObject == null)
+                               Deleted (this, e);
+                       else
+                               synchronizingObject.BeginInvoke (Deleted, new object[] { this, e });
                }
 
-               protected void OnError (ErrorEventArgs e)
+               internal void OnError (ErrorEventArgs e)
                {
-                       RaiseEvent (Error, e, EventType.ErrorEvent);
+                       if (Error == null)
+                               return;
+
+                       if (synchronizingObject == null)
+                               Error (this, e);
+                       else
+                               synchronizingObject.BeginInvoke (Error, new object[] { this, e });
                }
 
                protected void OnRenamed (RenamedEventArgs e)
                {
-                       RaiseEvent (Renamed, e, EventType.RenameEvent);
+                       if (Renamed == null)
+                               return;
+
+                       if (synchronizingObject == null)
+                               Renamed (this, e);
+                       else
+                               synchronizingObject.BeginInvoke (Renamed, new object[] { this, e });
                }
 
                public WaitForChangedResult WaitForChanged (WatcherChangeTypes changeType)
index 2b96de344040d978b619c85325c675b32259d703..f3cc7dcc183200a81a8ca409b3ab9e49f936f7ec 100644 (file)
@@ -4,6 +4,7 @@
 // Authors:
 //     Geoff Norton (gnorton@customerdna.com)
 //     Cody Russell (cody@xamarin.com)
+//     Alexis Christoforides (lexas@xamarin.com)
 //
 // (c) 2004 Geoff Norton
 // Copyright 2014 Xamarin Inc
@@ -36,6 +37,7 @@ using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Threading;
+using System.Reflection;
 
 namespace System.IO {
 
@@ -73,6 +75,7 @@ namespace System.IO {
                 VM = -11
         }
 
+       [Flags]
        enum FilterFlags : uint {
                 ReadPoll          = EventFlags.Flag0,
                 ReadOutOfBand     = EventFlags.Flag1,
@@ -128,7 +131,7 @@ namespace System.IO {
 
        [StructLayout(LayoutKind.Sequential)]
        struct kevent : IDisposable {
-               public int ident;
+               public UIntPtr ident;
                public EventFilter filter;
                public EventFlags flags;
                public FilterFlags fflags;
@@ -140,17 +143,21 @@ namespace System.IO {
                        if (udata != IntPtr.Zero)
                                Marshal.FreeHGlobal (udata);
                }
+
+
        }
 
+       [StructLayout(LayoutKind.Sequential)]
        struct timespec {
-               public int tv_sec;
-               public int tv_usec;
+               public IntPtr tv_sec;
+               public IntPtr tv_usec;
        }
 
        class PathData
        {
                public string Path;
                public bool IsDirectory;
+               public int Fd;
        }
 
        class KqueueMonitor : IDisposable
@@ -168,211 +175,367 @@ namespace System.IO {
 
                public void Dispose ()
                {
-                       Stop ();
+                       CleanUp ();
                }
 
                public void Start ()
                {
-                       conn = kqueue ();
+                       lock (stateLock) {
+                               if (started)
+                                       return;
 
-                       if (thread == null) {
-                               thread = new Thread (new ThreadStart (Monitor));
+                               conn = kqueue ();
+
+                               if (conn == -1)
+                                       throw new IOException (String.Format (
+                                               "kqueue() error at init, error code = '{0}'", Marshal.GetLastWin32Error ()));
+                                       
+                               thread = new Thread (() => DoMonitor ());
                                thread.IsBackground = true;
                                thread.Start ();
-                       }
 
-                       var pathData = Add (fsw.FullPath);
+                               startedEvent.WaitOne ();
 
-                       Scan (pathData);
+                               if (failedInit) {
+                                       thread.Join ();
+                                       CleanUp ();
+                                       throw new IOException ("Monitor thread failed while initializing.");
+                               }
+                               else 
+                                       started = true;
+                       }
                }
 
                public void Stop ()
                {
-                       stop = true;
-
-                       if (thread != null)
-                               thread.Interrupt ();
+                       lock (stateLock) {
+                               if (!started)
+                                       return;
+                                       
+                               requestStop = true;
+                               thread.Join ();
+                               requestStop = false;
+
+                               CleanUp ();
+                               started = false;
+                       }
+               }
 
+               void CleanUp ()
+               {
                        if (conn != -1)
                                close (conn);
 
                        conn = -1;
+
+                       foreach (int fd in fdsDict.Keys)
+                               close (fd); 
+
+                       fdsDict.Clear ();
+                       pathsDict.Clear ();
                }
 
-               private PathData FindPath (string path)
+               void DoMonitor ()
                {
-                       foreach (KeyValuePair<PathData, int> kv in paths) {
-                               if (kv.Key.Path == path)
-                                       return kv.Key;
+                       Exception exc = null;
+                       failedInit = false;
+
+                       try {
+                               Setup ();
+                       } catch (Exception e) {
+                               failedInit = true;
+                               exc = e;
+                       } finally {
+                               startedEvent.Set ();
+                       }
+
+                       if (failedInit) {
+                               fsw.OnError (new ErrorEventArgs (exc));
+                               return;
                        }
 
-                       return null;
+                       try {
+                               Monitor ();
+                       } catch (Exception e) {
+                               exc = e;
+                       } finally {
+                               if (!requestStop) { // failure
+                                       CleanUp ();
+                                       started = false;
+                               }
+                               if (exc != null)
+                                       fsw.OnError (new ErrorEventArgs (exc));
+                       }
                }
 
-               private PathData FindPath (int fd)
+               void Setup ()
+               {       
+                       var initialFds = new List<int> ();
+
+                       // GetFilenameFromFd() returns the *realpath* which can be different than fsw.FullPath because symlinks.
+                       // If so, introduce a fixup step.
+                       int fd = open (fsw.FullPath, O_EVTONLY, 0);
+                       var resolvedFullPath = GetFilenameFromFd (fd);
+                       close (fd);
+
+                       if (resolvedFullPath != fsw.FullPath)
+                               fixupPath = resolvedFullPath;
+                       else
+                               fixupPath = null;
+
+                       Scan (fsw.FullPath, false, ref initialFds);
+
+                       var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_usec = (IntPtr)0 };
+                       var eventBuffer = new kevent[0]; // we don't want to take any events from the queue at this point
+                       var changes = CreateChangeList (ref initialFds);
+
+                       int numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref immediate_timeout);
+
+                       if (numEvents == -1) {
+                               var errMsg = String.Format ("kevent() error at initial event registration, error code = '{0}'", Marshal.GetLastWin32Error ());
+                               throw new IOException (errMsg);
+                       }
+               }
+
+               kevent[] CreateChangeList (ref List<int> FdList)
                {
-                       foreach (KeyValuePair<PathData, int> kv in paths) {
-                               if (kv.Value == fd)
-                                       return kv.Key;
+                       if (FdList.Count == 0)
+                               return emptyEventList;
+
+                       var changes = new List<kevent> ();
+                       foreach (int fd in FdList) {
+                               var change = new kevent {
+
+                                       ident = (UIntPtr)fd,
+                                       filter = EventFilter.Vnode,
+                                       flags = EventFlags.Add | EventFlags.Enable | EventFlags.Clear,
+                                       fflags = FilterFlags.VNodeDelete | FilterFlags.VNodeExtend |
+                                               FilterFlags.VNodeRename | FilterFlags.VNodeAttrib |
+                                               FilterFlags.VNodeLink | FilterFlags.VNodeRevoke |
+                                               FilterFlags.VNodeWrite,
+                                       data = IntPtr.Zero,
+                                       udata = IntPtr.Zero
+                               };
+
+                               changes.Add (change);
                        }
+                       FdList.Clear ();
 
-                       return null;
+                       return changes.ToArray ();
                }
 
-               private void Monitor ()
+               void Monitor ()
                {
-                       bool firstRun = true;
+                       var timeout = new timespec { tv_sec = (IntPtr)0, tv_usec = (IntPtr)500000000 };
+                       var eventBuffer = new kevent[32];
+                       var newFds = new List<int> ();
+                       List<PathData> removeQueue = new List<PathData> ();
+                       List<string> rescanQueue = new List<string> ();
 
-                       while (!stop) {
-                               removeQueue.ForEach (Remove);
+                       while (!requestStop) {
+                               var changes = CreateChangeList (ref newFds);
 
-                               var changes = new List<kevent> ();
-                               var outEvents = new List<kevent> ();
+                               int numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref timeout);
 
-                               rescanQueue.ForEach (fd => {
-                                       var path = FindPath (fd);
-                                       Scan (path, !firstRun);
-                               });
-                               rescanQueue.Clear ();
-
-                               foreach (KeyValuePair<PathData, int> kv in paths) {
-                                       var change = new kevent {
-                                               ident  = kv.Value,
-                                               filter = EventFilter.Vnode,
-                                               flags  = EventFlags.Add | EventFlags.Enable | EventFlags.Clear,
-                                               fflags = FilterFlags.VNodeDelete | FilterFlags.VNodeExtend |
-                                                        FilterFlags.VNodeRename | FilterFlags.VNodeAttrib |
-                                                        FilterFlags.VNodeLink | FilterFlags.VNodeRevoke |
-                                                        FilterFlags.VNodeWrite,
-                                               data   = IntPtr.Zero,
-                                               udata  = IntPtr.Zero
-                                       };
-
-                                       changes.Add (change);
-                                       outEvents.Add (new kevent ());
+                               if (numEvents == -1) {
+                                       var errMsg = String.Format ("kevent() error, error code = '{0}'", Marshal.GetLastWin32Error ());
+                                       fsw.OnError (new ErrorEventArgs (new IOException (errMsg)));
                                }
 
-                               if (changes.Count > 0) {
-                                       var outArray = outEvents.ToArray ();
-                                       var changesArray = changes.ToArray ();
-                                       int numEvents = kevent (conn, changesArray, changesArray.Length, outArray, outArray.Length, IntPtr.Zero);
-
-                                       for (var i = 0; i < numEvents; i++) {
-                                               var kevt = outArray [i];
-                                               var pathData = FindPath (kevt.ident);
-
-                                               if ((kevt.fflags & FilterFlags.VNodeDelete) != 0) {
-                                                       removeQueue.Add (kevt.ident);
-                                                       PostEvent (FileAction.Removed, pathData.Path);
-                                               } else if (((kevt.fflags & FilterFlags.VNodeRename) != 0) || ((kevt.fflags & FilterFlags.VNodeRevoke) != 0) || ((kevt.fflags & FilterFlags.VNodeWrite) != 0)) {
-                                                       if (pathData.IsDirectory && Directory.Exists (pathData.Path))
-                                                               rescanQueue.Add (kevt.ident);
-
-                                                       if ((kevt.fflags & FilterFlags.VNodeRename) != 0) {
-                                                               var fd = paths [pathData];
-                                                               var newFilename = GetFilenameFromFd (fd);
-                                                               var oldFilename = pathData.Path;
-
-                                                               Remove (pathData);
-                                                               PostEvent (FileAction.RenamedNewName, oldFilename, newFilename);
-
-                                                               Add (newFilename, false);
-                                                       }
-                                               } else if ((kevt.fflags & FilterFlags.VNodeAttrib) != 0) {
-                                                       PostEvent (FileAction.Modified, pathData.Path);
-                                               }
+                               if (numEvents == 0)
+                                       continue;
+
+                               for (var i = 0; i < numEvents; i++) {
+                                       var kevt = eventBuffer [i];
+                                       var pathData = fdsDict [(int)kevt.ident];
+
+                                       if ((kevt.flags & EventFlags.Error) == EventFlags.Error) {
+                                               var errMsg = String.Format ("kevent() error watching path '{0}', error code = '{1}'", pathData.Path, kevt.data);
+                                               fsw.OnError (new ErrorEventArgs (new IOException (errMsg)));
+                                               continue;
                                        }
-                               } else {
-                                       Thread.Sleep (500);
+
+                                       if ((kevt.fflags & FilterFlags.VNodeDelete) == FilterFlags.VNodeDelete || (kevt.fflags & FilterFlags.VNodeRevoke) == FilterFlags.VNodeRevoke)
+                                               removeQueue.Add (pathData);
+
+                                       else if ((kevt.fflags & FilterFlags.VNodeWrite) == FilterFlags.VNodeWrite) {
+                                               if (pathData.IsDirectory)
+                                                       rescanQueue.Add (pathData.Path);
+                                               else
+                                                       PostEvent (FileAction.Modified, pathData.Path);
+                                       } 
+
+                                       else if ((kevt.fflags & FilterFlags.VNodeRename) == FilterFlags.VNodeRename) {
+                                               var newFilename = GetFilenameFromFd (pathData.Fd);
+
+                                               if (newFilename.StartsWith (fsw.FullPath))
+                                                       Rename (pathData, newFilename);
+                                               else //moved outside of our watched dir so stop watching
+                                                               RemoveTree (pathData);
+                                       } 
+
+                                       else if ((kevt.fflags & FilterFlags.VNodeAttrib) == FilterFlags.VNodeAttrib || (kevt.fflags & FilterFlags.VNodeExtend) == FilterFlags.VNodeExtend)
+                                               PostEvent (FileAction.Modified, pathData.Path);
                                }
 
-                               firstRun = false;
+                               removeQueue.ForEach (Remove);
+                               removeQueue.Clear ();
+
+                               rescanQueue.ForEach (path => {
+                                       Scan (path, true, ref newFds);
+                               });
+                               rescanQueue.Clear ();
                        }
                }
 
-               private PathData Add (string path, bool postEvents = false)
+               PathData Add (string path, bool postEvents, ref List<int> fds)
                {
+                       PathData pathData;
+                       pathsDict.TryGetValue (path, out pathData);
+
+                       if (pathData != null)
+                               return pathData;
+
                        var fd = open (path, O_EVTONLY, 0);
 
-                       if (fd == -1)
+                       if (fd == -1) {
+                               fsw.OnError (new ErrorEventArgs (new IOException (String.Format (
+                                       "open() error while attempting to process path '{0}', error code = '{1}'", path, Marshal.GetLastWin32Error ()))));
                                return null;
+                       }
 
-                       var attrs = File.GetAttributes (path);
-                       bool isDir = false;
-                       if ((attrs & FileAttributes.Directory) == FileAttributes.Directory)
-                               isDir = true;
+                       try {
+                               fds.Add (fd);
 
-                       var pathData = new PathData {
-                               Path = path,
-                               IsDirectory = isDir
-                       };
+                               var attrs = File.GetAttributes (path);
 
-                       if (FindPath (path) == null) {
-                               paths.Add (pathData, fd);
+                               pathData = new PathData {
+                                       Path = path,
+                                       Fd = fd,
+                                       IsDirectory = (attrs & FileAttributes.Directory) == FileAttributes.Directory
+                               };
+                               
+                               pathsDict.Add (path, pathData);
+                               fdsDict.Add (fd, pathData);
 
                                if (postEvents)
                                        PostEvent (FileAction.Added, path);
+
+                               return pathData;
+                       } catch (Exception e) {
+                               close (fd);
+                               fsw.OnError (new ErrorEventArgs (e));
+                               return null;
                        }
 
-                       return pathData;
                }
 
-               private void Remove (int fd)
+               void Remove (PathData pathData)
                {
-                       var path = FindPath (fd);
-                       paths.Remove (path);
-                       removeQueue.Remove (fd);
-
-                       close (fd);
+                       fdsDict.Remove (pathData.Fd);
+                       pathsDict.Remove (pathData.Path);
+                       close (pathData.Fd);
+                       PostEvent (FileAction.Removed, pathData.Path);
                }
 
-               private void Remove (PathData pathData)
+               void RemoveTree (PathData pathData)
                {
-                       var fd = paths [pathData];
-                       paths.Remove (pathData);
-                       removeQueue.Remove (fd);
+                       var toRemove = new List<PathData> ();
 
-                       close (fd);
+                       toRemove.Add (pathData);
+
+                       if (pathData.IsDirectory) {
+                               var prefix = pathData.Path + Path.DirectorySeparatorChar;
+                               foreach (var path in pathsDict.Keys)
+                                       if (path.StartsWith (prefix)) {
+                                               toRemove.Add (pathsDict [path]);
+                                       }
+                       }
+                       toRemove.ForEach (Remove);
                }
 
-               private void Scan (PathData pathData, bool postEvents = false)
+               void Rename (PathData pathData, string newRoot)
                {
-                       var path = pathData.Path;
+                       var toRename = new List<PathData> ();
+                       var oldRoot = pathData.Path;
+
+                       toRename.Add (pathData);
+                                                                                                                       
+                       if (pathData.IsDirectory) {
+                               var prefix = oldRoot + Path.DirectorySeparatorChar;
+                               foreach (var path in pathsDict.Keys)
+                                       if (path.StartsWith (prefix))
+                                               toRename.Add (pathsDict [path]);
+                       }
 
-                       Add (path, postEvents);
+                       toRename.ForEach ((pd) => { 
+                               var oldPath = pd.Path;
+                               var newPath = newRoot + oldPath.Substring (oldRoot.Length);
+                               pd.Path = newPath;
+                               pathsDict.Remove (oldPath);
+                               pathsDict.Add (newPath, pd);
+                       });
 
-                       if (!fsw.IncludeSubdirectories)
+                       PostEvent (FileAction.RenamedNewName, oldRoot, newRoot);
+               }
+
+               void Scan (string path, bool postEvents, ref List<int> fds)
+               {
+                       if (requestStop)
+                               return;
+                               
+                       var pathData = Add (path, postEvents, ref fds);
+
+                       if (pathData == null)
+                               return;
+                               
+                       if (!pathData.IsDirectory)
                                return;
 
-                       var attrs = File.GetAttributes (path);
-                       if ((attrs & FileAttributes.Directory) == FileAttributes.Directory) {
-                               var dirsToProcess = new List<string> ();
-                               dirsToProcess.Add (path);
+                       var dirsToProcess = new List<string> ();
+                       dirsToProcess.Add (path);
+
+                       while (dirsToProcess.Count > 0) {
+                               var tmp = dirsToProcess [0];
+                               dirsToProcess.RemoveAt (0);
+
+                               var info = new DirectoryInfo (tmp);
+                               FileSystemInfo[] fsInfos = null;
+                               try {
+                                       fsInfos = info.GetFileSystemInfos ();
+                                               
+                               } catch (IOException) {
+                                       // this can happen if the directory has been deleted already.
+                                       // that's okay, just keep processing the other dirs.
+                                       fsInfos = new FileSystemInfo[0];
+                               }
 
-                               while (dirsToProcess.Count > 0) {
-                                       var tmp = dirsToProcess [0];
-                                       dirsToProcess.RemoveAt (0);
+                               foreach (var fsi in fsInfos) {
+                                       if ((fsi.Attributes & FileAttributes.Directory) == FileAttributes.Directory && !fsw.IncludeSubdirectories)
+                                               continue;
 
-                                       var info = new DirectoryInfo (tmp);
-                                       foreach (var fsi in info.GetFileSystemInfos ()) {
-                                               if (Add (fsi.FullName, postEvents) == null)
-                                                       continue;
+                                       if ((fsi.Attributes & FileAttributes.Directory) != FileAttributes.Directory && !fsw.Pattern.IsMatch (fsi.FullName))
+                                               continue;
 
-                                               var childAttrs = File.GetAttributes (fsi.FullName);
-                                               if ((childAttrs & FileAttributes.Directory) == FileAttributes.Directory)
-                                                       dirsToProcess.Add (fsi.FullName);
-                                       }
+                                       var currentPathData = Add (fsi.FullName, postEvents, ref fds);
+
+                                       if (currentPathData != null && currentPathData.IsDirectory)
+                                               dirsToProcess.Add (fsi.FullName);
                                }
                        }
                }
-
-               private void PostEvent (FileAction action, string path, string newPath = null)
+                       
+               void PostEvent (FileAction action, string path, string newPath = null)
                {
                        RenamedEventArgs renamed = null;
 
                        if (action == 0)
                                return;
 
+                       // only post events that match filter pattern. check both old and new paths for renames
+                       if (!fsw.Pattern.IsMatch (path) && (newPath == null || !fsw.Pattern.IsMatch (newPath))) 
+                               return;
+                               
                        if (action == FileAction.RenamedNewName)
                                renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, "", newPath, path);
 
@@ -388,23 +551,36 @@ namespace System.IO {
 
                private string GetFilenameFromFd (int fd)
                {
-                       var sb = new StringBuilder (1024);
+                       var sb = new StringBuilder (__DARWIN_MAXPATHLEN);
 
-                       if (fcntl (fd, F_GETPATH, sb) != -1)
+                       if (fcntl (fd, F_GETPATH, sb) != -1) {
+                               if (fixupPath != null)
+                                       sb.Replace (fixupPath, fsw.FullPath, 0, fixupPath.Length); // see Setup()
                                return sb.ToString ();
-                       else
+                       } else {
+                               fsw.OnError (new ErrorEventArgs (new IOException (String.Format (
+                                       "fcntl() error while attempting to get path for fd '{0}', error code = '{1}'", fd, Marshal.GetLastWin32Error ()))));
                                return String.Empty;
+                       }
                }
 
-               private const int O_EVTONLY = 0x8000;
-               private const int F_GETPATH = 50;
-               private FileSystemWatcher fsw;
-               private int conn;
-               private Thread thread;
-               private bool stop;
-               private readonly List<int> removeQueue = new List<int> ();
-               private readonly List<int> rescanQueue = new List<int> ();
-               private readonly Dictionary<PathData, int> paths = new Dictionary<PathData, int> ();
+               const int O_EVTONLY = 0x8000;
+               const int F_GETPATH = 50;
+               const int __DARWIN_MAXPATHLEN = 1024;
+               static readonly kevent[] emptyEventList = new System.IO.kevent[0];
+
+               FileSystemWatcher fsw;
+               int conn;
+               Thread thread;
+               volatile bool requestStop = false;
+               AutoResetEvent startedEvent = new AutoResetEvent (false);
+               bool started = false;
+               bool failedInit = false;
+               object stateLock = new object ();
+
+               readonly Dictionary<string, PathData> pathsDict = new Dictionary<string, PathData> ();
+               readonly Dictionary<int, PathData> fdsDict = new Dictionary<int, PathData> ();
+               string fixupPath = null;
 
                [DllImport ("libc", EntryPoint="fcntl", CharSet=CharSet.Auto, SetLastError=true)]
                static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb);
@@ -419,7 +595,7 @@ namespace System.IO {
                extern static int kqueue ();
 
                [DllImport ("libc")]
-               extern static int kevent(int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, IntPtr time);
+               extern static int kevent (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, [In] ref timespec time);
        }
 
        class KeventWatcher : IFileWatcher
@@ -467,10 +643,9 @@ namespace System.IO {
                                monitor = (KqueueMonitor)watches [fsw];
                        } else {
                                monitor = new KqueueMonitor (fsw);
+                               watches.Add (fsw, monitor);
                        }
-
-                       watches.Add (fsw, monitor);
-
+                               
                        monitor.Start ();
                }
 
@@ -482,8 +657,7 @@ namespace System.IO {
 
                        monitor.Stop ();
                }
-
-
+                       
                [DllImport ("libc")]
                extern static int close (int fd);
 
index 0fbab4e5e7eb3e50be08b27174d1d8efb8b7c6bf..f302dffaaa046940f430fbec3a04b6dd78948660 100644 (file)
@@ -47,7 +47,7 @@ namespace System.IO {
                        Compile (pattern);
                }
 
-               // OSX has a retarded case-insensitive yet case-aware filesystem
+               // OSX has a case-insensitive yet case-aware filesystem
                // so we need a overload in here for the Kqueue watcher
                public bool IsMatch (string text, bool ignorecase)
                {
@@ -55,20 +55,17 @@ namespace System.IO {
                                bool match = String.Compare (pattern, text, ignorecase) == 0;
                                if (match)
                                        return true;
-                               
-                               // This is a special case for FSW. It needs to match e.g. subdir/file.txt
-                               // when the pattern is "file.txt"
-                               int idx = text.LastIndexOf ('/');
-                               if (idx == -1)
-                                       return false;
-                               idx++;
-                               if (idx == text.Length)
-                                       return false;
-                               
-                               return (String.Compare (pattern, text.Substring (idx), ignorecase) == 0);
                        }
+                               
+                       // This is a special case for FSW. It needs to match e.g. subdir/file.txt
+                       // when the pattern is "file.txt"
+                       var fileName = Path.GetFileName (text);
+                       
+                       if (!hasWildcard)
+                               return (String.Compare (pattern, fileName, ignorecase) == 0);
+                       
                        
-                       return Match (ops, text, 0);
+                       return Match (ops, fileName, 0);
                }
 
                public bool IsMatch (string text)