Merge pull request #2417 from razzfazz/guard_substr
[mono.git] / mcs / class / System / System.IO / KeventWatcher.cs
1 // 
2 // System.IO.KeventWatcher.cs: interface with osx kevent
3 //
4 // Authors:
5 //      Geoff Norton (gnorton@customerdna.com)
6 //      Cody Russell (cody@xamarin.com)
7 //      Alexis Christoforides (lexas@xamarin.com)
8 //
9 // (c) 2004 Geoff Norton
10 // Copyright 2014 Xamarin Inc
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.Collections;
34 using System.Collections.Generic;
35 using System.ComponentModel;
36 using System.Runtime.CompilerServices;
37 using System.Runtime.InteropServices;
38 using System.Text;
39 using System.Threading;
40 using System.Reflection;
41
42 namespace System.IO {
43
44         [Flags]
45         enum EventFlags : ushort {
46                 Add         = 0x0001,
47                 Delete      = 0x0002,
48                 Enable      = 0x0004,
49                 Disable     = 0x0008,
50                 OneShot     = 0x0010,
51                 Clear       = 0x0020,
52                 Receipt     = 0x0040,
53                 Dispatch    = 0x0080,
54
55                 Flag0       = 0x1000,
56                 Flag1       = 0x2000,
57                 SystemFlags = unchecked (0xf000),
58                         
59                 // Return values.
60                 EOF         = 0x8000,
61                 Error       = 0x4000,
62         }
63         
64         enum EventFilter : short {
65                 Read = -1,
66                 Write = -2,
67                 Aio = -3,
68                 Vnode = -4,
69                 Proc = -5,
70                 Signal = -6,
71                 Timer = -7,
72                 MachPort = -8,
73                 FS = -9,
74                 User = -10,
75                 VM = -11
76         }
77
78         [Flags]
79         enum FilterFlags : uint {
80                 ReadPoll          = EventFlags.Flag0,
81                 ReadOutOfBand     = EventFlags.Flag1,
82                 ReadLowWaterMark  = 0x00000001,
83
84                 WriteLowWaterMark = ReadLowWaterMark,
85
86                 NoteTrigger       = 0x01000000,
87                 NoteFFNop         = 0x00000000,
88                 NoteFFAnd         = 0x40000000,
89                 NoteFFOr          = 0x80000000,
90                 NoteFFCopy        = 0xc0000000,
91                 NoteFFCtrlMask    = 0xc0000000,
92                 NoteFFlagsMask    = 0x00ffffff,
93                                   
94                 VNodeDelete       = 0x00000001,
95                 VNodeWrite        = 0x00000002,
96                 VNodeExtend       = 0x00000004,
97                 VNodeAttrib       = 0x00000008,
98                 VNodeLink         = 0x00000010,
99                 VNodeRename       = 0x00000020,
100                 VNodeRevoke       = 0x00000040,
101                 VNodeNone         = 0x00000080,
102                                   
103                 ProcExit          = 0x80000000,
104                 ProcFork          = 0x40000000,
105                 ProcExec          = 0x20000000,
106                 ProcReap          = 0x10000000,
107                 ProcSignal        = 0x08000000,
108                 ProcExitStatus    = 0x04000000,
109                 ProcResourceEnd   = 0x02000000,
110
111                 // iOS only
112                 ProcAppactive     = 0x00800000,
113                 ProcAppBackground = 0x00400000,
114                 ProcAppNonUI      = 0x00200000,
115                 ProcAppInactive   = 0x00100000,
116                 ProcAppAllStates  = 0x00f00000,
117
118                 // Masks
119                 ProcPDataMask     = 0x000fffff,
120                 ProcControlMask   = 0xfff00000,
121
122                 VMPressure        = 0x80000000,
123                 VMPressureTerminate = 0x40000000,
124                 VMPressureSuddenTerminate = 0x20000000,
125                 VMError           = 0x10000000,
126                 TimerSeconds      =    0x00000001,
127                 TimerMicroSeconds =   0x00000002,
128                 TimerNanoSeconds  =   0x00000004,
129                 TimerAbsolute     =   0x00000008,
130         }
131
132         [StructLayout(LayoutKind.Sequential)]
133         struct kevent : IDisposable {
134                 public UIntPtr ident;
135                 public EventFilter filter;
136                 public EventFlags flags;
137                 public FilterFlags fflags;
138                 public IntPtr data;
139                 public IntPtr udata;
140
141                 public void Dispose ()
142                 {
143                         if (udata != IntPtr.Zero)
144                                 Marshal.FreeHGlobal (udata);
145                 }
146
147
148         }
149
150         [StructLayout(LayoutKind.Sequential)]
151         struct timespec {
152                 public IntPtr tv_sec;
153                 public IntPtr tv_nsec;
154         }
155
156         class PathData
157         {
158                 public string Path;
159                 public bool IsDirectory;
160                 public int Fd;
161         }
162
163         class KqueueMonitor : IDisposable
164         {
165                 static bool initialized;
166                 
167                 public int Connection
168                 {
169                         get { return conn; }
170                 }
171
172                 public KqueueMonitor (FileSystemWatcher fsw)
173                 {
174                         this.fsw = fsw;
175                         this.conn = -1;
176                         if (!initialized){
177                                 int t;
178                                 initialized = true;
179                                 var maxenv = Environment.GetEnvironmentVariable ("MONO_DARWIN_WATCHER_MAXFDS");
180                                 if (maxenv != null && Int32.TryParse (maxenv, out t))
181                                         maxFds = t;
182                         }
183                 }
184
185                 public void Dispose ()
186                 {
187                         CleanUp ();
188                 }
189
190                 public void Start ()
191                 {
192                         lock (stateLock) {
193                                 if (started)
194                                         return;
195
196                                 conn = kqueue ();
197
198                                 if (conn == -1)
199                                         throw new IOException (String.Format (
200                                                 "kqueue() error at init, error code = '{0}'", Marshal.GetLastWin32Error ()));
201                                         
202                                 thread = new Thread (() => DoMonitor ());
203                                 thread.IsBackground = true;
204                                 thread.Start ();
205
206                                 startedEvent.WaitOne ();
207
208                                 if (exc != null) {
209                                         thread.Join ();
210                                         CleanUp ();
211                                         throw exc;
212                                 }
213  
214                                 started = true;
215                         }
216                 }
217
218                 public void Stop ()
219                 {
220                         lock (stateLock) {
221                                 if (!started)
222                                         return;
223                                         
224                                 requestStop = true;
225
226                                 if (inDispatch)
227                                         return;
228                                 // This will break the wait in Monitor ()
229                                 lock (connLock) {
230                                         if (conn != -1)
231                                                 close (conn);
232                                         conn = -1;
233                                 }
234
235                                 if (!thread.Join (2000))
236                                         thread.Abort ();
237
238                                 requestStop = false;
239                                 started = false;
240
241                                 if (exc != null)
242                                         throw exc;
243                         }
244                 }
245
246                 void CleanUp ()
247                 {
248                         lock (connLock) {
249                                 if (conn != -1)
250                                         close (conn);
251                                 conn = -1;
252                         }
253
254                         foreach (int fd in fdsDict.Keys)
255                                 close (fd); 
256
257                         fdsDict.Clear ();
258                         pathsDict.Clear ();
259                 }
260
261                 void DoMonitor ()
262                 {                       
263                         try {
264                                 Setup ();
265                         } catch (Exception e) {
266                                 exc = e;
267                         } finally {
268                                 startedEvent.Set ();
269                         }
270
271                         if (exc != null) {
272                                 fsw.DispatchErrorEvents (new ErrorEventArgs (exc));
273                                 return;
274                         }
275
276                         try {
277                                 Monitor ();
278                         } catch (Exception e) {
279                                 exc = e;
280                         } finally {
281                                 CleanUp ();
282                                 if (!requestStop) { // failure
283                                         started = false;
284                                         inDispatch = false;
285                                         fsw.EnableRaisingEvents = false;
286                                 }
287                                 if (exc != null)
288                                         fsw.DispatchErrorEvents (new ErrorEventArgs (exc));
289                                 requestStop = false;
290                         }
291                 }
292
293                 void Setup ()
294                 {       
295                         var initialFds = new List<int> ();
296
297                         // fsw.FullPath may end in '/', see https://bugzilla.xamarin.com/show_bug.cgi?id=5747
298                         if (fsw.FullPath != "/" && fsw.FullPath.EndsWith ("/", StringComparison.Ordinal))
299                                 fullPathNoLastSlash = fsw.FullPath.Substring (0, fsw.FullPath.Length - 1);
300                         else
301                                 fullPathNoLastSlash = fsw.FullPath;
302                                 
303                         // realpath() returns the *realpath* which can be different than fsw.FullPath because symlinks.
304                         // If so, introduce a fixup step.
305                         var sb = new StringBuilder (__DARWIN_MAXPATHLEN);
306                         if (realpath(fsw.FullPath, sb) == IntPtr.Zero) {
307                                 var errMsg = String.Format ("realpath({0}) failed, error code = '{1}'", fsw.FullPath, Marshal.GetLastWin32Error ());
308                                 throw new IOException (errMsg);
309                         }
310                         var resolvedFullPath = sb.ToString();
311
312                         if (resolvedFullPath != fullPathNoLastSlash)
313                                 fixupPath = resolvedFullPath;
314                         else
315                                 fixupPath = null;
316
317                         Scan (fullPathNoLastSlash, false, ref initialFds);
318
319                         var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_nsec = (IntPtr)0 };
320                         var eventBuffer = new kevent[0]; // we don't want to take any events from the queue at this point
321                         var changes = CreateChangeList (ref initialFds);
322
323                         int numEvents;
324                         int errno = 0;
325                         do {
326                                 numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref immediate_timeout);
327                                 if (numEvents == -1) {
328                                         errno = Marshal.GetLastWin32Error ();
329                                 }
330                         } while (numEvents == -1 && errno == EINTR);
331
332                         if (numEvents == -1) {
333                                 var errMsg = String.Format ("kevent() error at initial event registration, error code = '{0}'", errno);
334                                 throw new IOException (errMsg);
335                         }
336                 }
337
338                 kevent[] CreateChangeList (ref List<int> FdList)
339                 {
340                         if (FdList.Count == 0)
341                                 return emptyEventList;
342
343                         var changes = new List<kevent> ();
344                         foreach (int fd in FdList) {
345                                 var change = new kevent {
346
347                                         ident = (UIntPtr)fd,
348                                         filter = EventFilter.Vnode,
349                                         flags = EventFlags.Add | EventFlags.Enable | EventFlags.Clear,
350                                         fflags = FilterFlags.VNodeDelete | FilterFlags.VNodeExtend |
351                                                 FilterFlags.VNodeRename | FilterFlags.VNodeAttrib |
352                                                 FilterFlags.VNodeLink | FilterFlags.VNodeRevoke |
353                                                 FilterFlags.VNodeWrite,
354                                         data = IntPtr.Zero,
355                                         udata = IntPtr.Zero
356                                 };
357
358                                 changes.Add (change);
359                         }
360                         FdList.Clear ();
361
362                         return changes.ToArray ();
363                 }
364
365                 void Monitor ()
366                 {
367                         var eventBuffer = new kevent[32];
368                         var newFds = new List<int> ();
369                         List<PathData> removeQueue = new List<PathData> ();
370                         List<string> rescanQueue = new List<string> ();
371
372                         int retries = 0; 
373
374                         while (!requestStop) {
375                                 var changes = CreateChangeList (ref newFds);
376
377                                 // We are calling an icall, so have to marshal manually
378                                 // Marshal in
379                                 int ksize = Marshal.SizeOf<kevent> ();
380                                 var changesNative = Marshal.AllocHGlobal (ksize * changes.Length);
381                                 for (int i = 0; i < changes.Length; ++i)
382                                         Marshal.StructureToPtr (changes [i], changesNative + (i * ksize), false);
383                                 var eventBufferNative = Marshal.AllocHGlobal (ksize * eventBuffer.Length);
384
385                                 int numEvents = kevent_notimeout (ref conn, changesNative, changes.Length, eventBufferNative, eventBuffer.Length);
386
387                                 // Marshal out
388                                 Marshal.FreeHGlobal (changesNative);
389                                 for (int i = 0; i < numEvents; ++i)
390                                         eventBuffer [i] = Marshal.PtrToStructure<kevent> (eventBufferNative + (i * ksize));
391                                 Marshal.FreeHGlobal (eventBufferNative);
392
393                                 if (numEvents == -1) {
394                                         // Stop () signals us to stop by closing the connection
395                                         if (requestStop)
396                                                 break;
397                                         int errno = Marshal.GetLastWin32Error ();
398                                         if (errno != EINTR && ++retries == 3)
399                                                 throw new IOException (String.Format (
400                                                         "persistent kevent() error, error code = '{0}'", errno));
401
402                                         continue;
403                                 }
404                                 retries = 0;
405
406                                 for (var i = 0; i < numEvents; i++) {
407                                         var kevt = eventBuffer [i];
408
409                                         if (!fdsDict.ContainsKey ((int)kevt.ident))
410                                                 // The event is for a file that was removed
411                                                 continue;
412
413                                         var pathData = fdsDict [(int)kevt.ident];
414
415                                         if ((kevt.flags & EventFlags.Error) == EventFlags.Error) {
416                                                 var errMsg = String.Format ("kevent() error watching path '{0}', error code = '{1}'", pathData.Path, kevt.data);
417                                                 fsw.DispatchErrorEvents (new ErrorEventArgs (new IOException (errMsg)));
418                                                 continue;
419                                         }
420                                                 
421                                         if ((kevt.fflags & FilterFlags.VNodeDelete) == FilterFlags.VNodeDelete || (kevt.fflags & FilterFlags.VNodeRevoke) == FilterFlags.VNodeRevoke) {
422                                                 if (pathData.Path == fullPathNoLastSlash)
423                                                         // The root path is deleted; exit silently
424                                                         return;
425                                                                 
426                                                 removeQueue.Add (pathData);
427                                                 continue;
428                                         }
429
430                                         if ((kevt.fflags & FilterFlags.VNodeRename) == FilterFlags.VNodeRename) {
431                                                         UpdatePath (pathData);
432                                         } 
433
434                                         if ((kevt.fflags & FilterFlags.VNodeWrite) == FilterFlags.VNodeWrite) {
435                                                 if (pathData.IsDirectory) //TODO: Check if dirs trigger Changed events on .NET
436                                                         rescanQueue.Add (pathData.Path);
437                                                 else
438                                                         PostEvent (FileAction.Modified, pathData.Path);
439                                         }
440                                                 
441                                         if ((kevt.fflags & FilterFlags.VNodeAttrib) == FilterFlags.VNodeAttrib || (kevt.fflags & FilterFlags.VNodeExtend) == FilterFlags.VNodeExtend)
442                                                 PostEvent (FileAction.Modified, pathData.Path);
443                                 }
444
445                                 removeQueue.ForEach (Remove);
446                                 removeQueue.Clear ();
447
448                                 rescanQueue.ForEach (path => {
449                                         Scan (path, true, ref newFds);
450                                 });
451                                 rescanQueue.Clear ();
452                         }
453                 }
454
455                 PathData Add (string path, bool postEvents, ref List<int> fds)
456                 {
457                         PathData pathData;
458                         pathsDict.TryGetValue (path, out pathData);
459
460                         if (pathData != null)
461                                 return pathData;
462
463                         if (fdsDict.Count >= maxFds)
464                                 throw new IOException ("kqueue() FileSystemWatcher has reached the maximum number of files to watch."); 
465
466                         var fd = open (path, O_EVTONLY, 0);
467
468                         if (fd == -1) {
469                                 fsw.DispatchErrorEvents (new ErrorEventArgs (new IOException (String.Format (
470                                         "open() error while attempting to process path '{0}', error code = '{1}'", path, Marshal.GetLastWin32Error ()))));
471                                 return null;
472                         }
473
474                         try {
475                                 fds.Add (fd);
476
477                                 var attrs = File.GetAttributes (path);
478
479                                 pathData = new PathData {
480                                         Path = path,
481                                         Fd = fd,
482                                         IsDirectory = (attrs & FileAttributes.Directory) == FileAttributes.Directory
483                                 };
484                                 
485                                 pathsDict.Add (path, pathData);
486                                 fdsDict.Add (fd, pathData);
487
488                                 if (postEvents)
489                                         PostEvent (FileAction.Added, path);
490
491                                 return pathData;
492                         } catch (Exception e) {
493                                 close (fd);
494                                 fsw.DispatchErrorEvents (new ErrorEventArgs (e));
495                                 return null;
496                         }
497
498                 }
499
500                 void Remove (PathData pathData)
501                 {
502                         fdsDict.Remove (pathData.Fd);
503                         pathsDict.Remove (pathData.Path);
504                         close (pathData.Fd);
505                         PostEvent (FileAction.Removed, pathData.Path);
506                 }
507
508                 void RemoveTree (PathData pathData)
509                 {
510                         var toRemove = new List<PathData> ();
511
512                         toRemove.Add (pathData);
513
514                         if (pathData.IsDirectory) {
515                                 var prefix = pathData.Path + Path.DirectorySeparatorChar;
516                                 foreach (var path in pathsDict.Keys)
517                                         if (path.StartsWith (prefix)) {
518                                                 toRemove.Add (pathsDict [path]);
519                                         }
520                         }
521                         toRemove.ForEach (Remove);
522                 }
523
524                 void UpdatePath (PathData pathData)
525                 {
526                         var newRoot = GetFilenameFromFd (pathData.Fd);
527                         if (!newRoot.StartsWith (fullPathNoLastSlash)) { // moved outside of our watched path (so stop observing it)
528                                 RemoveTree (pathData);
529                                 return;
530                         }
531                                 
532                         var toRename = new List<PathData> ();
533                         var oldRoot = pathData.Path;
534
535                         toRename.Add (pathData);
536                                                                                                                         
537                         if (pathData.IsDirectory) { // anything under the directory must have their paths updated
538                                 var prefix = oldRoot + Path.DirectorySeparatorChar;
539                                 foreach (var path in pathsDict.Keys)
540                                         if (path.StartsWith (prefix))
541                                                 toRename.Add (pathsDict [path]);
542                         }
543                 
544                         foreach (var renaming in toRename) {
545                                 var oldPath = renaming.Path;
546                                 var newPath = newRoot + oldPath.Substring (oldRoot.Length);
547
548                                 renaming.Path = newPath;
549                                 pathsDict.Remove (oldPath);
550
551                                 // destination may exist in our records from a Created event, take care of it
552                                 if (pathsDict.ContainsKey (newPath)) {
553                                         var conflict = pathsDict [newPath];
554                                         if (GetFilenameFromFd (renaming.Fd) == GetFilenameFromFd (conflict.Fd))
555                                                 Remove (conflict);
556                                         else
557                                                 UpdatePath (conflict);
558                                 }
559                                         
560                                 pathsDict.Add (newPath, renaming);
561                         }
562                         
563                         PostEvent (FileAction.RenamedNewName, oldRoot, newRoot);
564                 }
565
566                 void Scan (string path, bool postEvents, ref List<int> fds)
567                 {
568                         if (requestStop)
569                                 return;
570                                 
571                         var pathData = Add (path, postEvents, ref fds);
572
573                         if (pathData == null)
574                                 return;
575                                 
576                         if (!pathData.IsDirectory)
577                                 return;
578
579                         var dirsToProcess = new List<string> ();
580                         dirsToProcess.Add (path);
581
582                         while (dirsToProcess.Count > 0) {
583                                 var tmp = dirsToProcess [0];
584                                 dirsToProcess.RemoveAt (0);
585
586                                 var info = new DirectoryInfo (tmp);
587                                 FileSystemInfo[] fsInfos = null;
588                                 try {
589                                         fsInfos = info.GetFileSystemInfos ();
590                                                 
591                                 } catch (IOException) {
592                                         // this can happen if the directory has been deleted already.
593                                         // that's okay, just keep processing the other dirs.
594                                         fsInfos = new FileSystemInfo[0];
595                                 }
596
597                                 foreach (var fsi in fsInfos) {
598                                         if ((fsi.Attributes & FileAttributes.Directory) == FileAttributes.Directory && !fsw.IncludeSubdirectories)
599                                                 continue;
600
601                                         if ((fsi.Attributes & FileAttributes.Directory) != FileAttributes.Directory && !fsw.Pattern.IsMatch (fsi.FullName))
602                                                 continue;
603
604                                         var currentPathData = Add (fsi.FullName, postEvents, ref fds);
605
606                                         if (currentPathData != null && currentPathData.IsDirectory)
607                                                 dirsToProcess.Add (fsi.FullName);
608                                 }
609                         }
610                 }
611                         
612                 void PostEvent (FileAction action, string path, string newPath = null)
613                 {
614                         RenamedEventArgs renamed = null;
615
616                         if (requestStop || action == 0)
617                                 return;
618
619                         // e.Name
620                         string name = (path.Length > fullPathNoLastSlash.Length) ? path.Substring (fullPathNoLastSlash.Length + 1) : String.Empty;
621
622                         // only post events that match filter pattern. check both old and new paths for renames
623                         if (!fsw.Pattern.IsMatch (path) && (newPath == null || !fsw.Pattern.IsMatch (newPath)))
624                                 return;
625                                 
626                         if (action == FileAction.RenamedNewName) {
627                                 string newName = (newPath.Length > fullPathNoLastSlash.Length) ? newPath.Substring (fullPathNoLastSlash.Length + 1) : String.Empty;
628                                 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, fsw.Path, newName, name);
629                         }
630                                 
631                         fsw.DispatchEvents (action, name, ref renamed);
632
633                         if (fsw.Waiting) {
634                                 lock (fsw) {
635                                         fsw.Waiting = false;
636                                         System.Threading.Monitor.PulseAll (fsw);
637                                 }
638                         }
639                 }
640
641                 private string GetFilenameFromFd (int fd)
642                 {
643                         var sb = new StringBuilder (__DARWIN_MAXPATHLEN);
644
645                         if (fcntl (fd, F_GETPATH, sb) != -1) {
646                                 if (fixupPath != null) 
647                                         sb.Replace (fixupPath, fullPathNoLastSlash, 0, fixupPath.Length); // see Setup()
648
649                                 return sb.ToString ();
650                         } else {
651                                 fsw.DispatchErrorEvents (new ErrorEventArgs (new IOException (String.Format (
652                                         "fcntl() error while attempting to get path for fd '{0}', error code = '{1}'", fd, Marshal.GetLastWin32Error ()))));
653                                 return String.Empty;
654                         }
655                 }
656
657                 const int O_EVTONLY = 0x8000;
658                 const int F_GETPATH = 50;
659                 const int __DARWIN_MAXPATHLEN = 1024;
660                 const int EINTR = 4;
661                 static readonly kevent[] emptyEventList = new System.IO.kevent[0];
662                 int maxFds = Int32.MaxValue;
663
664                 FileSystemWatcher fsw;
665                 int conn;
666                 Thread thread;
667                 volatile bool requestStop = false;
668                 AutoResetEvent startedEvent = new AutoResetEvent (false);
669                 bool started = false;
670                 bool inDispatch = false;
671                 Exception exc = null;
672                 object stateLock = new object ();
673                 object connLock = new object ();
674
675                 readonly Dictionary<string, PathData> pathsDict = new Dictionary<string, PathData> ();
676                 readonly Dictionary<int, PathData> fdsDict = new Dictionary<int, PathData> ();
677                 string fixupPath = null;
678                 string fullPathNoLastSlash = null;
679
680                 [DllImport ("libc", CharSet=CharSet.Auto, SetLastError=true)]
681                 static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb);
682
683                 [DllImport ("libc", CharSet=CharSet.Auto, SetLastError=true)]
684                 static extern IntPtr realpath (string pathname, StringBuilder sb);
685
686                 [DllImport ("libc", SetLastError=true)]
687                 extern static int open (string path, int flags, int mode_t);
688
689                 [DllImport ("libc")]
690                 extern static int close (int fd);
691
692                 [DllImport ("libc", SetLastError=true)]
693                 extern static int kqueue ();
694
695                 [DllImport ("libc", SetLastError=true)]
696                 extern static int kevent (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, [In] ref timespec time);
697
698                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
699                 extern static int kevent_notimeout (ref int kq, IntPtr ev, int nchanges, IntPtr evtlist, int nevents);
700         }
701
702         class KeventWatcher : IFileWatcher
703         {
704                 static bool failed;
705                 static KeventWatcher instance;
706                 static Hashtable watches;  // <FileSystemWatcher, KqueueMonitor>
707
708                 private KeventWatcher ()
709                 {
710                 }
711
712                 // Locked by caller
713                 public static bool GetInstance (out IFileWatcher watcher)
714                 {
715                         if (failed == true) {
716                                 watcher = null;
717                                 return false;
718                         }
719
720                         if (instance != null) {
721                                 watcher = instance;
722                                 return true;
723                         }
724
725                         watches = Hashtable.Synchronized (new Hashtable ());
726                         var conn = kqueue();
727                         if (conn == -1) {
728                                 failed = true;
729                                 watcher = null;
730                                 return false;
731                         }
732                         close (conn);
733
734                         instance = new KeventWatcher ();
735                         watcher = instance;
736                         return true;
737                 }
738
739                 public void StartDispatching (FileSystemWatcher fsw)
740                 {
741                         KqueueMonitor monitor;
742
743                         if (watches.ContainsKey (fsw)) {
744                                 monitor = (KqueueMonitor)watches [fsw];
745                         } else {
746                                 monitor = new KqueueMonitor (fsw);
747                                 watches.Add (fsw, monitor);
748                         }
749                                 
750                         monitor.Start ();
751                 }
752
753                 public void StopDispatching (FileSystemWatcher fsw)
754                 {
755                         KqueueMonitor monitor = (KqueueMonitor)watches [fsw];
756                         if (monitor == null)
757                                 return;
758
759                         monitor.Stop ();
760                 }
761                         
762                 [DllImport ("libc")]
763                 extern static int close (int fd);
764
765                 [DllImport ("libc")]
766                 extern static int kqueue ();
767         }
768 }
769