@@ -, +, @@ --- mcs/class/System/System.IO/KeventWatcher.cs | 67 ++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 21 deletions(-) --- b/mcs/class/System/System.IO/KeventWatcher.cs +++ b/mcs/class/System/System.IO/KeventWatcher.cs @@ -150,7 +150,7 @@ public void Dispose () [StructLayout(LayoutKind.Sequential)] struct timespec { public IntPtr tv_sec; - public IntPtr tv_usec; + public IntPtr tv_nsec; } class PathData @@ -223,8 +223,8 @@ public void Stop () conn = -1; } - if (!thread.Join (2000)) - thread.Abort (); + while (!thread.Join (2000)) + thread.Interrupt (); requestStop = false; started = false; @@ -291,11 +291,10 @@ void Setup () else fullPathNoLastSlash = fsw.FullPath; - // GetFilenameFromFd() returns the *realpath* which can be different than fsw.FullPath because symlinks. + // realpath() returns the *realpath* which can be different than fsw.FullPath because symlinks. // If so, introduce a fixup step. - int fd = open (fullPathNoLastSlash, O_EVTONLY, 0); - var resolvedFullPath = GetFilenameFromFd (fd); - close (fd); + var sb = new StringBuilder (__DARWIN_MAXPATHLEN); + var resolvedFullPath = (realpath(fsw.FullPath, sb) == IntPtr.Zero) ? "" : sb.ToString(); if (resolvedFullPath != fullPathNoLastSlash) fixupPath = resolvedFullPath; @@ -304,14 +303,21 @@ void Setup () Scan (fullPathNoLastSlash, false, ref initialFds); - var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_usec = (IntPtr)0 }; + var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_nsec = (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); + int numEvents; + int errno = 0; + do { + numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref immediate_timeout); + if (numEvents == -1) { + errno = Marshal.GetLastWin32Error (); + } + } while (numEvents == -1 && errno == EINTR); if (numEvents == -1) { - var errMsg = String.Format ("kevent() error at initial event registration, error code = '{0}'", Marshal.GetLastWin32Error ()); + var errMsg = String.Format ("kevent() error at initial event registration, error code = '{0}'", errno); throw new IOException (errMsg); } } @@ -361,9 +367,10 @@ void Monitor () // Stop () signals us to stop by closing the connection if (requestStop) break; - if (++retries == 3) + int errno = Marshal.GetLastWin32Error (); + if (errno != EINTR && ++retries == 3) throw new IOException (String.Format ( - "persistent kevent() error, error code = '{0}'", Marshal.GetLastWin32Error ())); + "persistent kevent() error, error code = '{0}'", errno)); continue; } @@ -395,8 +402,20 @@ void Monitor () } if ((kevt.fflags & FilterFlags.VNodeRename) == FilterFlags.VNodeRename) { - UpdatePath (pathData); - } + /* We can simply remove the entire subtree here, as + the move will trigger a directory update and thus + a re-scan at the new location, which will cause any + children to be re-added. */ + removeQueue.Add (pathData); + if (pathData.IsDirectory) { + var prefix = pathData.Path + Path.DirectorySeparatorChar; + foreach (var path in pathsDict.Keys) + if (path.StartsWith (prefix)) { + removeQueue.Add (pathsDict [path]); + } + } + PostEvent (FileAction.RenamedOldName, pathData.Path); + } if ((kevt.fflags & FilterFlags.VNodeWrite) == FilterFlags.VNodeWrite) { if (pathData.IsDirectory) //TODO: Check if dirs trigger Changed events on .NET @@ -584,15 +603,17 @@ void PostEvent (FileAction action, string path, string newPath = null) return; // e.Name - string name = path.Substring (fullPathNoLastSlash.Length + 1); + string name = (path.Length > fullPathNoLastSlash.Length) ? path.Substring (fullPathNoLastSlash.Length + 1) : String.Empty; // 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) { - string newName = newPath.Substring (fullPathNoLastSlash.Length + 1); + string newName = (newPath.Length > fullPathNoLastSlash.Length) ? newPath.Substring (fullPathNoLastSlash.Length + 1) : String.Empty; renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, fsw.Path, newName, name); + } else if (action == FileAction.RenamedOldName) { + renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, fsw.Path, null, name); } fsw.DispatchEvents (action, name, ref renamed); @@ -624,6 +645,7 @@ private string GetFilenameFromFd (int fd) const int O_EVTONLY = 0x8000; const int F_GETPATH = 50; const int __DARWIN_MAXPATHLEN = 1024; + const int EINTR = 4; static readonly kevent[] emptyEventList = new System.IO.kevent[0]; const int maxFds = 200; @@ -643,22 +665,25 @@ private string GetFilenameFromFd (int fd) string fixupPath = null; string fullPathNoLastSlash = null; - [DllImport ("libc", EntryPoint="fcntl", CharSet=CharSet.Auto, SetLastError=true)] + [DllImport ("libc", CharSet=CharSet.Auto, SetLastError=true)] static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb); - [DllImport ("libc")] + [DllImport ("libc", SetLastError=true)] extern static int open (string path, int flags, int mode_t); + [DllImport ("libc", CharSet=CharSet.Auto, SetLastError=true)] + static extern IntPtr realpath (string pathname, StringBuilder sb); + [DllImport ("libc")] extern static int close (int fd); - [DllImport ("libc")] + [DllImport ("libc", SetLastError=true)] extern static int kqueue (); - [DllImport ("libc")] + [DllImport ("libc", SetLastError=true)] extern static int kevent (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, [In] ref timespec time); - [DllImport ("libc", EntryPoint="kevent")] + [DllImport ("libc", EntryPoint="kevent", SetLastError=true)] extern static int kevent_notimeout (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, IntPtr ptr); }