Merge pull request #1456 from directhex/fix_Microsoft.Build.Execution.BuildManagerTes...
[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_usec;
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                 public int Connection
166                 {
167                         get { return conn; }
168                 }
169
170                 public KqueueMonitor (FileSystemWatcher fsw)
171                 {
172                         this.fsw = fsw;
173                         this.conn = -1;
174                 }
175
176                 public void Dispose ()
177                 {
178                         CleanUp ();
179                 }
180
181                 public void Start ()
182                 {
183                         lock (stateLock) {
184                                 if (started)
185                                         return;
186
187                                 conn = kqueue ();
188
189                                 if (conn == -1)
190                                         throw new IOException (String.Format (
191                                                 "kqueue() error at init, error code = '{0}'", Marshal.GetLastWin32Error ()));
192                                         
193                                 thread = new Thread (() => DoMonitor ());
194                                 thread.IsBackground = true;
195                                 thread.Start ();
196
197                                 startedEvent.WaitOne ();
198
199                                 if (exc != null) {
200                                         thread.Join ();
201                                         CleanUp ();
202                                         throw exc;
203                                 }
204  
205                                 started = true;
206                         }
207                 }
208
209                 public void Stop ()
210                 {
211                         lock (stateLock) {
212                                 if (!started)
213                                         return;
214                                         
215                                 requestStop = true;
216
217                                 if (inDispatch)
218                                         return;
219                                 if (!thread.Join (2000))
220                                         thread.Abort ();
221
222                                 requestStop = false;
223                                 started = false;
224
225                                 if (exc != null)
226                                         throw exc;
227                         }
228                 }
229
230                 void CleanUp ()
231                 {
232                         if (conn != -1)
233                                 close (conn);
234
235                         conn = -1;
236
237                         foreach (int fd in fdsDict.Keys)
238                                 close (fd); 
239
240                         fdsDict.Clear ();
241                         pathsDict.Clear ();
242                 }
243
244                 void DoMonitor ()
245                 {                       
246                         try {
247                                 Setup ();
248                         } catch (Exception e) {
249                                 exc = e;
250                         } finally {
251                                 startedEvent.Set ();
252                         }
253
254                         if (exc != null) {
255                                 fsw.DispatchErrorEvents (new ErrorEventArgs (exc));
256                                 return;
257                         }
258
259                         try {
260                                 Monitor ();
261                         } catch (Exception e) {
262                                 exc = e;
263                         } finally {
264                                 CleanUp ();
265                                 if (!requestStop) { // failure
266                                         started = false;
267                                         inDispatch = false;
268                                         fsw.EnableRaisingEvents = false;
269                                         throw exc;
270                                 }
271                                 if (exc != null)
272                                         fsw.DispatchErrorEvents (new ErrorEventArgs (exc));
273                                 requestStop = false;
274                         }
275                 }
276
277                 void Setup ()
278                 {       
279                         var initialFds = new List<int> ();
280
281                         // fsw.FullPath may end in '/', see https://bugzilla.xamarin.com/show_bug.cgi?id=5747
282                         if (fsw.FullPath.EndsWith ("/", StringComparison.Ordinal))
283                                 fullPathNoLastSlash = fsw.FullPath.Substring (0, fsw.FullPath.Length - 1);
284                         else
285                                 fullPathNoLastSlash = fsw.FullPath;
286                                 
287                         // GetFilenameFromFd() returns the *realpath* which can be different than fsw.FullPath because symlinks.
288                         // If so, introduce a fixup step.
289                         int fd = open (fullPathNoLastSlash, O_EVTONLY, 0);
290                         var resolvedFullPath = GetFilenameFromFd (fd);
291                         close (fd);
292
293                         if (resolvedFullPath != fullPathNoLastSlash)
294                                 fixupPath = resolvedFullPath;
295                         else
296                                 fixupPath = null;
297
298                         Scan (fullPathNoLastSlash, false, ref initialFds);
299
300                         var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_usec = (IntPtr)0 };
301                         var eventBuffer = new kevent[0]; // we don't want to take any events from the queue at this point
302                         var changes = CreateChangeList (ref initialFds);
303
304                         int numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref immediate_timeout);
305
306                         if (numEvents == -1) {
307                                 var errMsg = String.Format ("kevent() error at initial event registration, error code = '{0}'", Marshal.GetLastWin32Error ());
308                                 throw new IOException (errMsg);
309                         }
310                 }
311
312                 kevent[] CreateChangeList (ref List<int> FdList)
313                 {
314                         if (FdList.Count == 0)
315                                 return emptyEventList;
316
317                         var changes = new List<kevent> ();
318                         foreach (int fd in FdList) {
319                                 var change = new kevent {
320
321                                         ident = (UIntPtr)fd,
322                                         filter = EventFilter.Vnode,
323                                         flags = EventFlags.Add | EventFlags.Enable | EventFlags.Clear,
324                                         fflags = FilterFlags.VNodeDelete | FilterFlags.VNodeExtend |
325                                                 FilterFlags.VNodeRename | FilterFlags.VNodeAttrib |
326                                                 FilterFlags.VNodeLink | FilterFlags.VNodeRevoke |
327                                                 FilterFlags.VNodeWrite,
328                                         data = IntPtr.Zero,
329                                         udata = IntPtr.Zero
330                                 };
331
332                                 changes.Add (change);
333                         }
334                         FdList.Clear ();
335
336                         return changes.ToArray ();
337                 }
338
339                 void Monitor ()
340                 {
341                         var timeout = new timespec { tv_sec = (IntPtr)0, tv_usec = (IntPtr)500000000 };
342                         var eventBuffer = new kevent[32];
343                         var newFds = new List<int> ();
344                         List<PathData> removeQueue = new List<PathData> ();
345                         List<string> rescanQueue = new List<string> ();
346
347                         int retries = 0; 
348
349                         while (!requestStop) {
350                                 var changes = CreateChangeList (ref newFds);
351
352                                 int numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref timeout);
353
354                                 if (numEvents == -1) {
355                                         if (++retries == 3)
356                                                 throw new IOException (String.Format (
357                                                         "persistent kevent() error, error code = '{0}'", Marshal.GetLastWin32Error ()));
358
359                                         continue;
360                                 }
361
362                                 retries = 0;
363
364                                 for (var i = 0; i < numEvents; i++) {
365                                         var kevt = eventBuffer [i];
366                                         var pathData = fdsDict [(int)kevt.ident];
367
368                                         if ((kevt.flags & EventFlags.Error) == EventFlags.Error) {
369                                                 var errMsg = String.Format ("kevent() error watching path '{0}', error code = '{1}'", pathData.Path, kevt.data);
370                                                 fsw.DispatchErrorEvents (new ErrorEventArgs (new IOException (errMsg)));
371                                                 continue;
372                                         }
373                                                 
374                                         if ((kevt.fflags & FilterFlags.VNodeDelete) == FilterFlags.VNodeDelete || (kevt.fflags & FilterFlags.VNodeRevoke) == FilterFlags.VNodeRevoke) {
375                                                 removeQueue.Add (pathData);
376                                                 continue;
377                                         }
378
379                                         if ((kevt.fflags & FilterFlags.VNodeRename) == FilterFlags.VNodeRename) {
380                                                         UpdatePath (pathData);
381                                         } 
382
383                                         if ((kevt.fflags & FilterFlags.VNodeWrite) == FilterFlags.VNodeWrite) {
384                                                 if (pathData.IsDirectory) //TODO: Check if dirs trigger Changed events on .NET
385                                                         rescanQueue.Add (pathData.Path);
386                                                 else
387                                                         PostEvent (FileAction.Modified, pathData.Path);
388                                         }
389                                                 
390                                         if ((kevt.fflags & FilterFlags.VNodeAttrib) == FilterFlags.VNodeAttrib || (kevt.fflags & FilterFlags.VNodeExtend) == FilterFlags.VNodeExtend)
391                                                 PostEvent (FileAction.Modified, pathData.Path);
392                                 }
393
394                                 removeQueue.ForEach (Remove);
395                                 removeQueue.Clear ();
396
397                                 rescanQueue.ForEach (path => {
398                                         Scan (path, true, ref newFds);
399                                 });
400                                 rescanQueue.Clear ();
401                         }
402                 }
403
404                 PathData Add (string path, bool postEvents, ref List<int> fds)
405                 {
406                         PathData pathData;
407                         pathsDict.TryGetValue (path, out pathData);
408
409                         if (pathData != null)
410                                 return pathData;
411
412                         if (fdsDict.Count >= maxFds)
413                                 throw new IOException ("kqueue() FileSystemWatcher has reached the maximum nunmber of files to watch."); 
414
415                         var fd = open (path, O_EVTONLY, 0);
416
417                         if (fd == -1) {
418                                 fsw.DispatchErrorEvents (new ErrorEventArgs (new IOException (String.Format (
419                                         "open() error while attempting to process path '{0}', error code = '{1}'", path, Marshal.GetLastWin32Error ()))));
420                                 return null;
421                         }
422
423                         try {
424                                 fds.Add (fd);
425
426                                 var attrs = File.GetAttributes (path);
427
428                                 pathData = new PathData {
429                                         Path = path,
430                                         Fd = fd,
431                                         IsDirectory = (attrs & FileAttributes.Directory) == FileAttributes.Directory
432                                 };
433                                 
434                                 pathsDict.Add (path, pathData);
435                                 fdsDict.Add (fd, pathData);
436
437                                 if (postEvents)
438                                         PostEvent (FileAction.Added, path);
439
440                                 return pathData;
441                         } catch (Exception e) {
442                                 close (fd);
443                                 fsw.DispatchErrorEvents (new ErrorEventArgs (e));
444                                 return null;
445                         }
446
447                 }
448
449                 void Remove (PathData pathData)
450                 {
451                         fdsDict.Remove (pathData.Fd);
452                         pathsDict.Remove (pathData.Path);
453                         close (pathData.Fd);
454                         PostEvent (FileAction.Removed, pathData.Path);
455                 }
456
457                 void RemoveTree (PathData pathData)
458                 {
459                         var toRemove = new List<PathData> ();
460
461                         toRemove.Add (pathData);
462
463                         if (pathData.IsDirectory) {
464                                 var prefix = pathData.Path + Path.DirectorySeparatorChar;
465                                 foreach (var path in pathsDict.Keys)
466                                         if (path.StartsWith (prefix)) {
467                                                 toRemove.Add (pathsDict [path]);
468                                         }
469                         }
470                         toRemove.ForEach (Remove);
471                 }
472
473                 void UpdatePath (PathData pathData)
474                 {
475                         var newRoot = GetFilenameFromFd (pathData.Fd);
476                         if (!newRoot.StartsWith (fullPathNoLastSlash)) { // moved outside of our watched path (so stop observing it)
477                                 RemoveTree (pathData);
478                                 return;
479                         }
480                                 
481                         var toRename = new List<PathData> ();
482                         var oldRoot = pathData.Path;
483
484                         toRename.Add (pathData);
485                                                                                                                         
486                         if (pathData.IsDirectory) { // anything under the directory must have their paths updated
487                                 var prefix = oldRoot + Path.DirectorySeparatorChar;
488                                 foreach (var path in pathsDict.Keys)
489                                         if (path.StartsWith (prefix))
490                                                 toRename.Add (pathsDict [path]);
491                         }
492                 
493                         foreach (var renaming in toRename) {
494                                 var oldPath = renaming.Path;
495                                 var newPath = newRoot + oldPath.Substring (oldRoot.Length);
496
497                                 renaming.Path = newPath;
498                                 pathsDict.Remove (oldPath);
499
500                                 // destination may exist in our records from a Created event, take care of it
501                                 if (pathsDict.ContainsKey (newPath)) {
502                                         var conflict = pathsDict [newPath];
503                                         if (GetFilenameFromFd (renaming.Fd) == GetFilenameFromFd (conflict.Fd))
504                                                 Remove (conflict);
505                                         else
506                                                 UpdatePath (conflict);
507                                 }
508                                         
509                                 pathsDict.Add (newPath, renaming);
510                         }
511                         
512                         PostEvent (FileAction.RenamedNewName, oldRoot, newRoot);
513                 }
514
515                 void Scan (string path, bool postEvents, ref List<int> fds)
516                 {
517                         if (requestStop)
518                                 return;
519                                 
520                         var pathData = Add (path, postEvents, ref fds);
521
522                         if (pathData == null)
523                                 return;
524                                 
525                         if (!pathData.IsDirectory)
526                                 return;
527
528                         var dirsToProcess = new List<string> ();
529                         dirsToProcess.Add (path);
530
531                         while (dirsToProcess.Count > 0) {
532                                 var tmp = dirsToProcess [0];
533                                 dirsToProcess.RemoveAt (0);
534
535                                 var info = new DirectoryInfo (tmp);
536                                 FileSystemInfo[] fsInfos = null;
537                                 try {
538                                         fsInfos = info.GetFileSystemInfos ();
539                                                 
540                                 } catch (IOException) {
541                                         // this can happen if the directory has been deleted already.
542                                         // that's okay, just keep processing the other dirs.
543                                         fsInfos = new FileSystemInfo[0];
544                                 }
545
546                                 foreach (var fsi in fsInfos) {
547                                         if ((fsi.Attributes & FileAttributes.Directory) == FileAttributes.Directory && !fsw.IncludeSubdirectories)
548                                                 continue;
549
550                                         if ((fsi.Attributes & FileAttributes.Directory) != FileAttributes.Directory && !fsw.Pattern.IsMatch (fsi.FullName))
551                                                 continue;
552
553                                         var currentPathData = Add (fsi.FullName, postEvents, ref fds);
554
555                                         if (currentPathData != null && currentPathData.IsDirectory)
556                                                 dirsToProcess.Add (fsi.FullName);
557                                 }
558                         }
559                 }
560                         
561                 void PostEvent (FileAction action, string path, string newPath = null)
562                 {
563                         RenamedEventArgs renamed = null;
564
565                         if (requestStop || action == 0)
566                                 return;
567
568                         // e.Name
569                         string name = path.Substring (fullPathNoLastSlash.Length + 1); 
570
571                         // only post events that match filter pattern. check both old and new paths for renames
572                         if (!fsw.Pattern.IsMatch (path) && (newPath == null || !fsw.Pattern.IsMatch (newPath)))
573                                 return;
574                                 
575                         if (action == FileAction.RenamedNewName) {
576                                 string newName = newPath.Substring (fullPathNoLastSlash.Length + 1);
577                                 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, fsw.Path, newName, name);
578                         }
579                                 
580                         fsw.DispatchEvents (action, name, ref renamed);
581
582                         if (fsw.Waiting) {
583                                 lock (fsw) {
584                                         fsw.Waiting = false;
585                                         System.Threading.Monitor.PulseAll (fsw);
586                                 }
587                         }
588                 }
589
590                 private string GetFilenameFromFd (int fd)
591                 {
592                         var sb = new StringBuilder (__DARWIN_MAXPATHLEN);
593
594                         if (fcntl (fd, F_GETPATH, sb) != -1) {
595                                 if (fixupPath != null) 
596                                         sb.Replace (fixupPath, fullPathNoLastSlash, 0, fixupPath.Length); // see Setup()
597
598                                 return sb.ToString ();
599                         } else {
600                                 fsw.DispatchErrorEvents (new ErrorEventArgs (new IOException (String.Format (
601                                         "fcntl() error while attempting to get path for fd '{0}', error code = '{1}'", fd, Marshal.GetLastWin32Error ()))));
602                                 return String.Empty;
603                         }
604                 }
605
606                 const int O_EVTONLY = 0x8000;
607                 const int F_GETPATH = 50;
608                 const int __DARWIN_MAXPATHLEN = 1024;
609                 static readonly kevent[] emptyEventList = new System.IO.kevent[0];
610                 const int maxFds = 200;
611
612                 FileSystemWatcher fsw;
613                 int conn;
614                 Thread thread;
615                 volatile bool requestStop = false;
616                 AutoResetEvent startedEvent = new AutoResetEvent (false);
617                 bool started = false;
618                 bool inDispatch = false;
619                 Exception exc = null;
620                 object stateLock = new object ();
621
622                 readonly Dictionary<string, PathData> pathsDict = new Dictionary<string, PathData> ();
623                 readonly Dictionary<int, PathData> fdsDict = new Dictionary<int, PathData> ();
624                 string fixupPath = null;
625                 string fullPathNoLastSlash = null;
626
627                 [DllImport ("libc", EntryPoint="fcntl", CharSet=CharSet.Auto, SetLastError=true)]
628                 static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb);
629
630                 [DllImport ("libc")]
631                 extern static int open (string path, int flags, int mode_t);
632
633                 [DllImport ("libc")]
634                 extern static int close (int fd);
635
636                 [DllImport ("libc")]
637                 extern static int kqueue ();
638
639                 [DllImport ("libc")]
640                 extern static int kevent (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, [In] ref timespec time);
641         }
642
643         class KeventWatcher : IFileWatcher
644         {
645                 static bool failed;
646                 static KeventWatcher instance;
647                 static Hashtable watches;  // <FileSystemWatcher, KqueueMonitor>
648
649                 private KeventWatcher ()
650                 {
651                 }
652
653                 // Locked by caller
654                 public static bool GetInstance (out IFileWatcher watcher)
655                 {
656                         if (failed == true) {
657                                 watcher = null;
658                                 return false;
659                         }
660
661                         if (instance != null) {
662                                 watcher = instance;
663                                 return true;
664                         }
665
666                         watches = Hashtable.Synchronized (new Hashtable ());
667                         var conn = kqueue();
668                         if (conn == -1) {
669                                 failed = true;
670                                 watcher = null;
671                                 return false;
672                         }
673                         close (conn);
674
675                         instance = new KeventWatcher ();
676                         watcher = instance;
677                         return true;
678                 }
679
680                 public void StartDispatching (FileSystemWatcher fsw)
681                 {
682                         KqueueMonitor monitor;
683
684                         if (watches.ContainsKey (fsw)) {
685                                 monitor = (KqueueMonitor)watches [fsw];
686                         } else {
687                                 monitor = new KqueueMonitor (fsw);
688                                 watches.Add (fsw, monitor);
689                         }
690                                 
691                         monitor.Start ();
692                 }
693
694                 public void StopDispatching (FileSystemWatcher fsw)
695                 {
696                         KqueueMonitor monitor = (KqueueMonitor)watches [fsw];
697                         if (monitor == null)
698                                 return;
699
700                         monitor.Stop ();
701                 }
702                         
703                 [DllImport ("libc")]
704                 extern static int close (int fd);
705
706                 [DllImport ("libc")]
707                 extern static int kqueue ();
708         }
709 }
710