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