Revert some whitespace changes.
[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 //
8 // (c) 2004 Geoff Norton
9 // Copyright 2014 Xamarin Inc
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.IO;
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
41 namespace System.IO {
42
43         [Flags]
44         enum EventFlags : ushort {
45                 Add         = 0x0001,
46                 Delete      = 0x0002,
47                 Enable      = 0x0004,
48                 Disable     = 0x0008,
49                 OneShot     = 0x0010,
50                 Clear       = 0x0020,
51                 Receipt     = 0x0040,
52                 Dispatch    = 0x0080,
53
54                 Flag0       = 0x1000,
55                 Flag1       = 0x2000,
56                 SystemFlags = unchecked (0xf000),
57                         
58                 // Return values.
59                 EOF         = 0x8000,
60                 Error       = 0x4000,
61         }
62         
63         enum EventFilter : short {
64                 Read = -1,
65                 Write = -2,
66                 Aio = -3,
67                 Vnode = -4,
68                 Proc = -5,
69                 Signal = -6,
70                 Timer = -7,
71                 MachPort = -8,
72                 FS = -9,
73                 User = -10,
74                 VM = -11
75         }
76
77         enum FilterFlags : uint {
78                 ReadPoll          = EventFlags.Flag0,
79                 ReadOutOfBand     = EventFlags.Flag1,
80                 ReadLowWaterMark  = 0x00000001,
81
82                 WriteLowWaterMark = ReadLowWaterMark,
83
84                 NoteTrigger       = 0x01000000,
85                 NoteFFNop         = 0x00000000,
86                 NoteFFAnd         = 0x40000000,
87                 NoteFFOr          = 0x80000000,
88                 NoteFFCopy        = 0xc0000000,
89                 NoteFFCtrlMask    = 0xc0000000,
90                 NoteFFlagsMask    = 0x00ffffff,
91                                   
92                 VNodeDelete       = 0x00000001,
93                 VNodeWrite        = 0x00000002,
94                 VNodeExtend       = 0x00000004,
95                 VNodeAttrib       = 0x00000008,
96                 VNodeLink         = 0x00000010,
97                 VNodeRename       = 0x00000020,
98                 VNodeRevoke       = 0x00000040,
99                 VNodeNone         = 0x00000080,
100                                   
101                 ProcExit          = 0x80000000,
102                 ProcFork          = 0x40000000,
103                 ProcExec          = 0x20000000,
104                 ProcReap          = 0x10000000,
105                 ProcSignal        = 0x08000000,
106                 ProcExitStatus    = 0x04000000,
107                 ProcResourceEnd   = 0x02000000,
108
109                 // iOS only
110                 ProcAppactive     = 0x00800000,
111                 ProcAppBackground = 0x00400000,
112                 ProcAppNonUI      = 0x00200000,
113                 ProcAppInactive   = 0x00100000,
114                 ProcAppAllStates  = 0x00f00000,
115
116                 // Masks
117                 ProcPDataMask     = 0x000fffff,
118                 ProcControlMask   = 0xfff00000,
119
120                 VMPressure        = 0x80000000,
121                 VMPressureTerminate = 0x40000000,
122                 VMPressureSuddenTerminate = 0x20000000,
123                 VMError           = 0x10000000,
124                 TimerSeconds      =    0x00000001,
125                 TimerMicroSeconds =   0x00000002,
126                 TimerNanoSeconds  =   0x00000004,
127                 TimerAbsolute     =   0x00000008,
128         }
129
130         [StructLayout(LayoutKind.Sequential)]
131         struct kevent : IDisposable {
132                 public int ident;
133                 public EventFilter filter;
134                 public EventFlags flags;
135                 public FilterFlags fflags;
136                 public IntPtr data;
137                 public IntPtr udata;
138
139                 public void Dispose ()
140                 {
141                         if (udata != IntPtr.Zero)
142                                 Marshal.FreeHGlobal (udata);
143                 }
144         }
145
146         struct timespec {
147                 public int tv_sec;
148                 public int tv_usec;
149         }
150
151         class KeventFileData {
152                 public FileSystemInfo fsi;
153                 public DateTime LastAccessTime;
154                 public DateTime LastWriteTime;
155
156                 public KeventFileData(FileSystemInfo fsi, DateTime LastAccessTime, DateTime LastWriteTime) {
157                         this.fsi = fsi;
158                         this.LastAccessTime = LastAccessTime;
159                         this.LastWriteTime = LastWriteTime;
160                 }
161         }
162
163         class KeventData {
164                 public FileSystemWatcher FSW;
165                 public string Path;
166                 public string FileMask;
167                 public bool IncludeSubdirs;
168                 public bool Enabled;
169                 public Hashtable DirEntries;
170                 public kevent ev;
171                 public int fd;
172                 public bool IsDirectory;
173         }
174
175         class KeventWatcher : IFileWatcher
176         {
177                 static bool failed;
178                 static KeventWatcher instance;
179                 static Hashtable watches;  // <FileSystemWatcher, KeventData>
180                 static Thread thread;
181                 static int conn;
182                 static bool stop;
183
184                 readonly Dictionary<string, KeventData> filenamesDict = new Dictionary<string, KeventData> ();
185                 readonly Dictionary<int, KeventData> fdsDict = new Dictionary<int, KeventData> ();
186                 readonly List<int> removeQueue = new List<int> ();
187                 readonly List<int> rescanQueue = new List<int> ();
188                 
189                 private KeventWatcher ()
190                 {
191                 }
192                 
193                 // Locked by caller
194                 public static bool GetInstance (out IFileWatcher watcher)
195                 {
196                         if (failed == true) {
197                                 watcher = null;
198                                 return false;
199                         }
200
201                         if (instance != null) {
202                                 watcher = instance;
203                                 return true;
204                         }
205
206                         watches = Hashtable.Synchronized (new Hashtable ());
207                         conn = kqueue();
208                         if (conn == -1) {
209                                 failed = true;
210                                 watcher = null;
211                                 return false;
212                         }
213
214                         instance = new KeventWatcher ();
215                         watcher = instance;
216                         return true;
217                 }
218                 
219                 public void StartDispatching (FileSystemWatcher fsw)
220                 {
221                         KeventData data;
222
223                         lock (this) {
224                                 if (thread == null) {
225                                         thread = new Thread (new ThreadStart (Monitor));
226                                         thread.IsBackground = true;
227                                         thread.Start ();
228                                 }
229
230                                 data = (KeventData) watches [fsw];
231                         }
232
233                         if (data == null) {
234                                 data = new KeventData ();
235                                 data.FSW = fsw;
236                                 data.Path = fsw.FullPath;
237                                 data.FileMask = fsw.MangledFilter;
238                                 data.IncludeSubdirs = fsw.IncludeSubdirectories;
239
240                                 data.Enabled = true;
241                                 lock (this) {
242                                         Scan (data);
243                                         watches [fsw] = data;
244                                         stop = false;
245                                 }
246                         }
247                 }
248
249                 bool Add (KeventData data, bool postEvents = false)
250                 {
251                         var path = data.Path;
252
253                         if (filenamesDict.ContainsKey (path) || fdsDict.ContainsKey (data.fd) ) {
254                                 return false;
255                         }
256
257                         var fd = open (path, 0x8000 /* O_EVTONLY */, 0);
258
259                         if (fd != -1) {
260                                 data.fd = fd;
261                                 filenamesDict.Add (path, data);
262                                 fdsDict.Add (fd, data);
263
264                                 var attrs = File.GetAttributes (data.Path);
265                                 data.IsDirectory = ((attrs & FileAttributes.Directory) == FileAttributes.Directory);
266
267                                 if (postEvents)
268                                         PostEvent (path, data.FSW, FileAction.Added, path);
269
270                                 return true;
271                         } else {
272                                 return false;
273                         }
274                 }
275
276                 void Remove (int fd)
277                 {
278                         if (!fdsDict.ContainsKey (fd))
279                                 return;
280
281                         var data = fdsDict [fd];
282                         fdsDict.Remove (fd);
283                         filenamesDict.Remove (data.Path);
284                         removeQueue.Remove (fd);
285
286                         close (fd);
287                 }
288
289                 void Remove (string path)
290                 {
291                         var data = filenamesDict [path];
292
293                         filenamesDict.Remove (path);
294                         fdsDict.Remove (data.fd);
295                         close (data.fd);
296                 }
297
298                 bool Scan (KeventData data, bool postEvents = false)
299                 {
300                         var path = data.Path;
301
302                         Add (data);
303                         if (!data.IncludeSubdirs) {
304                                 return true;
305                         }
306
307                         if (data.IsDirectory && !Directory.Exists (path))
308                                 return false;
309
310                         var attrs = File.GetAttributes (path);
311                         if ((attrs & FileAttributes.Directory) == FileAttributes.Directory) {
312                                 var dirs_to_process = new List<string> ();
313                                 dirs_to_process.Add (path);
314
315                                 while (dirs_to_process.Count > 0) {
316                                         var tmp_path = dirs_to_process [0];
317                                         dirs_to_process.RemoveAt (0);
318                                         var dirinfo = new DirectoryInfo (tmp_path);
319                                         foreach (var fsi in dirinfo.GetFileSystemInfos ()) {
320                                                 var newdata = new KeventData {
321                                                         Path = fsi.FullName,
322                                                         FileMask = data.FileMask,
323                                                         FSW = data.FSW,
324                                                         IncludeSubdirs = data.IncludeSubdirs
325                                                 };
326
327                                                 if (!Add (newdata, postEvents))
328                                                         continue;
329
330                                                 var childAttrs = File.GetAttributes (fsi.FullName);
331                                                 if ((childAttrs & FileAttributes.Directory) == FileAttributes.Directory)
332                                                         dirs_to_process.Add (fsi.FullName);
333                                         }
334                                 }
335                         }
336
337                         return true;
338                 }
339
340                 public void StopDispatching (FileSystemWatcher fsw)
341                 {
342                         KeventData data;
343                         lock (this) {
344                                 data = (KeventData) watches [fsw];
345                                 if (data == null)
346                                         return;
347
348                                 StopMonitoringDirectory (data);
349                                 watches.Remove (fsw);
350                                 if (watches.Count == 0)
351                                         stop = true;
352
353                                 if (!data.IncludeSubdirs)
354                                         return;
355
356                         }
357                 }
358
359                 static void StopMonitoringDirectory (KeventData data)
360                 {
361                         close(data.ev.ident);
362                 }
363
364                 void Monitor ()
365                 {
366                         bool firstRun = true;
367
368                         while (!stop) {
369                                 removeQueue.ForEach (Remove);
370
371                                 rescanQueue.ForEach (
372                                         fd => {
373                                                 var data = fdsDict[fd];
374                                                 Scan (data, !firstRun);
375
376                                                 rescanQueue.Remove (fd);
377                                         }
378                                 );
379
380                                 foreach (KeventData data in watches.Values) {
381                                         Scan (data);
382                                 }
383
384                                 var changes = new List<kevent> ();
385                                 var outEvents = new List<kevent> ();
386
387                                 foreach (KeyValuePair<int, KeventData> kv in fdsDict) {
388                                         var change = new kevent {
389                                                 ident = kv.Key,
390                                                 filter = EventFilter.Vnode,
391                                                 flags = EventFlags.Add | EventFlags.Enable | EventFlags.Clear,
392                                                 fflags = FilterFlags.VNodeDelete | FilterFlags.VNodeExtend | FilterFlags.VNodeRename | FilterFlags.VNodeAttrib | FilterFlags.VNodeLink | FilterFlags.VNodeRevoke | FilterFlags.VNodeWrite,
393                                                 data = IntPtr.Zero,
394                                                 udata = IntPtr.Zero
395                                         };
396
397                                         changes.Add (change);
398                                         outEvents.Add (new kevent());
399                                 }
400
401                                 if (changes.Count > 0) {
402                                         int numEvents = 0;
403                                         var out_array = outEvents.ToArray ();
404
405                                         lock (this) {
406                                                 kevent[] changes_array = changes.ToArray ();
407                                                 numEvents = kevent (conn, changes_array, changes_array.Length, out_array, out_array.Length, IntPtr.Zero);
408                                         }
409
410                                         for (var i = 0; i < numEvents; i++) {
411                                                 var kevt = out_array [i];
412                                                 if ((kevt.flags & EventFlags.Error) == EventFlags.Error)
413                                                         throw new Exception ("kevent error");
414
415                                                 if ((kevt.fflags & FilterFlags.VNodeDelete) != 0) {
416                                                         removeQueue.Add (kevt.ident);
417                                                         var data = fdsDict [kevt.ident];
418                                                         PostEvent (data.Path, data.FSW, FileAction.Removed, data.Path);
419                                                 } else if (((kevt.fflags & FilterFlags.VNodeRename) != 0) || ((kevt.fflags & FilterFlags.VNodeRevoke) != 0) || ((kevt.fflags & FilterFlags.VNodeWrite) != 0)) {
420                                                         var data = fdsDict [kevt.ident];
421                                                         if (data.IsDirectory && Directory.Exists (data.Path))
422                                                                 rescanQueue.Add (kevt.ident);
423
424                                                         if ((kevt.fflags & FilterFlags.VNodeRename) != 0) {
425                                                                 var newFilename = GetFilenameFromFd (data.fd);
426                                                                 Remove (data.fd);
427                                                                 PostEvent (data.Path, data.FSW, FileAction.RenamedNewName, data.Path, newFilename);
428
429                                                                 var newEvent = new KeventData {
430                                                                         Path = newFilename,
431                                                                         FileMask = data.FileMask,
432                                                                         FSW = data.FSW,
433                                                                         IncludeSubdirs = data.IncludeSubdirs
434                                                                 };
435
436                                                                 Add (newEvent, false);
437                                                         }
438                                                 } else if ((kevt.fflags & FilterFlags.VNodeAttrib) != 0) {
439                                                         var data = fdsDict[kevt.ident];
440                                                         PostEvent (data.Path, data.FSW, FileAction.Modified, data.Path);
441                                                 }
442                                         }
443                                 } else {
444                                         Thread.Sleep (500);
445                                 }
446
447                                 firstRun = false;
448                         }
449
450                         lock (this) {
451                                 thread = null;
452                                 stop = false;
453                         }
454                 }
455
456                 private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, string fullname, string newname = null)
457                 {
458                         RenamedEventArgs renamed = null;
459
460                         if (fa == 0)
461                                 return;
462
463                         if (fa == FileAction.RenamedNewName)
464                                 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, "", newname, fullname);
465
466                         lock (fsw) {
467                                 fsw.DispatchEvents (fa, filename, ref renamed);
468                                 if (fsw.Waiting) {
469                                         fsw.Waiting = false;
470                                         System.Threading.Monitor.PulseAll (fsw);
471                                 }
472                         }
473                 }
474
475                 private string GetFilenameFromFd (int fd)
476                 {
477                         var sb = new StringBuilder (1024);
478
479                         if (fcntl (fd, 50 /* F_GETPATH */, sb) != -1) {
480                                 return sb.ToString ();
481                         } else {
482                                 return String.Empty;
483                         }
484                 }
485
486                 [DllImport("libc", EntryPoint="fcntl", CharSet=CharSet.Auto, SetLastError=true)]
487                 public static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb);
488
489                 [DllImport ("libc")]
490                 extern static int open(string path, int flags, int mode_t);
491                 
492                 [DllImport ("libc")]
493                 extern static int close(int fd);
494
495                 [DllImport ("libc")]
496                 extern static int kqueue();
497
498                 [DllImport ("libc")]
499                 extern static int kevent(int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, IntPtr time);
500         }
501 }