2 // System.IO.KeventWatcher.cs: interface with osx kevent
5 // Geoff Norton (gnorton@customerdna.com)
6 // Cody Russell (cody@xamarin.com)
8 // (c) 2004 Geoff Norton
9 // Copyright 2014 Xamarin Inc
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
33 using System.Collections;
34 using System.Collections.Generic;
35 using System.ComponentModel;
36 using System.Runtime.CompilerServices;
37 using System.Runtime.InteropServices;
39 using System.Threading;
44 enum EventFlags : ushort {
56 SystemFlags = unchecked (0xf000),
63 enum EventFilter : short {
77 enum FilterFlags : uint {
78 ReadPoll = EventFlags.Flag0,
79 ReadOutOfBand = EventFlags.Flag1,
80 ReadLowWaterMark = 0x00000001,
82 WriteLowWaterMark = ReadLowWaterMark,
84 NoteTrigger = 0x01000000,
85 NoteFFNop = 0x00000000,
86 NoteFFAnd = 0x40000000,
87 NoteFFOr = 0x80000000,
88 NoteFFCopy = 0xc0000000,
89 NoteFFCtrlMask = 0xc0000000,
90 NoteFFlagsMask = 0x00ffffff,
92 VNodeDelete = 0x00000001,
93 VNodeWrite = 0x00000002,
94 VNodeExtend = 0x00000004,
95 VNodeAttrib = 0x00000008,
96 VNodeLink = 0x00000010,
97 VNodeRename = 0x00000020,
98 VNodeRevoke = 0x00000040,
99 VNodeNone = 0x00000080,
101 ProcExit = 0x80000000,
102 ProcFork = 0x40000000,
103 ProcExec = 0x20000000,
104 ProcReap = 0x10000000,
105 ProcSignal = 0x08000000,
106 ProcExitStatus = 0x04000000,
107 ProcResourceEnd = 0x02000000,
110 ProcAppactive = 0x00800000,
111 ProcAppBackground = 0x00400000,
112 ProcAppNonUI = 0x00200000,
113 ProcAppInactive = 0x00100000,
114 ProcAppAllStates = 0x00f00000,
117 ProcPDataMask = 0x000fffff,
118 ProcControlMask = 0xfff00000,
120 VMPressure = 0x80000000,
121 VMPressureTerminate = 0x40000000,
122 VMPressureSuddenTerminate = 0x20000000,
123 VMError = 0x10000000,
124 TimerSeconds = 0x00000001,
125 TimerMicroSeconds = 0x00000002,
126 TimerNanoSeconds = 0x00000004,
127 TimerAbsolute = 0x00000008,
130 [StructLayout(LayoutKind.Sequential)]
131 struct kevent : IDisposable {
133 public EventFilter filter;
134 public EventFlags flags;
135 public FilterFlags fflags;
139 public void Dispose ()
141 if (udata != IntPtr.Zero)
142 Marshal.FreeHGlobal (udata);
151 class KeventFileData {
152 public FileSystemInfo fsi;
153 public DateTime LastAccessTime;
154 public DateTime LastWriteTime;
156 public KeventFileData(FileSystemInfo fsi, DateTime LastAccessTime, DateTime LastWriteTime) {
158 this.LastAccessTime = LastAccessTime;
159 this.LastWriteTime = LastWriteTime;
164 public FileSystemWatcher FSW;
166 public string FileMask;
167 public bool IncludeSubdirs;
169 public Hashtable DirEntries;
172 public bool IsDirectory;
175 class KeventWatcher : IFileWatcher
178 static KeventWatcher instance;
179 static Hashtable watches; // <FileSystemWatcher, KeventData>
180 static Thread thread;
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> ();
189 private KeventWatcher ()
194 public static bool GetInstance (out IFileWatcher watcher)
196 if (failed == true) {
201 if (instance != null) {
206 watches = Hashtable.Synchronized (new Hashtable ());
214 instance = new KeventWatcher ();
219 public void StartDispatching (FileSystemWatcher fsw)
224 if (thread == null) {
225 thread = new Thread (new ThreadStart (Monitor));
226 thread.IsBackground = true;
230 data = (KeventData) watches [fsw];
234 data = new KeventData ();
236 data.Path = fsw.FullPath;
237 data.FileMask = fsw.MangledFilter;
238 data.IncludeSubdirs = fsw.IncludeSubdirectories;
243 watches [fsw] = data;
249 bool Add (KeventData data, bool postEvents = false)
251 var path = data.Path;
253 if (filenamesDict.ContainsKey (path) || fdsDict.ContainsKey (data.fd) ) {
257 var fd = open (path, 0x8000 /* O_EVTONLY */, 0);
261 filenamesDict.Add (path, data);
262 fdsDict.Add (fd, data);
264 var attrs = File.GetAttributes (data.Path);
265 data.IsDirectory = ((attrs & FileAttributes.Directory) == FileAttributes.Directory);
268 PostEvent (path, data.FSW, FileAction.Added, path);
278 if (!fdsDict.ContainsKey (fd))
281 var data = fdsDict [fd];
283 filenamesDict.Remove (data.Path);
284 removeQueue.Remove (fd);
289 void Remove (string path)
291 var data = filenamesDict [path];
293 filenamesDict.Remove (path);
294 fdsDict.Remove (data.fd);
298 bool Scan (KeventData data, bool postEvents = false)
300 var path = data.Path;
303 if (!data.IncludeSubdirs) {
307 if (data.IsDirectory && !Directory.Exists (path))
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);
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 {
322 FileMask = data.FileMask,
324 IncludeSubdirs = data.IncludeSubdirs
327 if (!Add (newdata, postEvents))
330 var childAttrs = File.GetAttributes (fsi.FullName);
331 if ((childAttrs & FileAttributes.Directory) == FileAttributes.Directory)
332 dirs_to_process.Add (fsi.FullName);
340 public void StopDispatching (FileSystemWatcher fsw)
344 data = (KeventData) watches [fsw];
348 StopMonitoringDirectory (data);
349 watches.Remove (fsw);
350 if (watches.Count == 0)
353 if (!data.IncludeSubdirs)
359 static void StopMonitoringDirectory (KeventData data)
361 close(data.ev.ident);
366 bool firstRun = true;
369 removeQueue.ForEach (Remove);
371 rescanQueue.ForEach (
373 var data = fdsDict[fd];
374 Scan (data, !firstRun);
376 rescanQueue.Remove (fd);
380 foreach (KeventData data in watches.Values) {
384 var changes = new List<kevent> ();
385 var outEvents = new List<kevent> ();
387 foreach (KeyValuePair<int, KeventData> kv in fdsDict) {
388 var change = new kevent {
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,
397 changes.Add (change);
398 outEvents.Add (new kevent());
401 if (changes.Count > 0) {
403 var out_array = outEvents.ToArray ();
406 kevent[] changes_array = changes.ToArray ();
407 numEvents = kevent (conn, changes_array, changes_array.Length, out_array, out_array.Length, IntPtr.Zero);
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");
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);
424 if ((kevt.fflags & FilterFlags.VNodeRename) != 0) {
425 var newFilename = GetFilenameFromFd (data.fd);
427 PostEvent (data.Path, data.FSW, FileAction.RenamedNewName, data.Path, newFilename);
429 var newEvent = new KeventData {
431 FileMask = data.FileMask,
433 IncludeSubdirs = data.IncludeSubdirs
436 Add (newEvent, false);
438 } else if ((kevt.fflags & FilterFlags.VNodeAttrib) != 0) {
439 var data = fdsDict[kevt.ident];
440 PostEvent (data.Path, data.FSW, FileAction.Modified, data.Path);
456 private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, string fullname, string newname = null)
458 RenamedEventArgs renamed = null;
463 if (fa == FileAction.RenamedNewName)
464 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, "", newname, fullname);
467 fsw.DispatchEvents (fa, filename, ref renamed);
470 System.Threading.Monitor.PulseAll (fsw);
475 private string GetFilenameFromFd (int fd)
477 var sb = new StringBuilder (1024);
479 if (fcntl (fd, 50 /* F_GETPATH */, sb) != -1) {
480 return sb.ToString ();
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);
490 extern static int open(string path, int flags, int mode_t);
493 extern static int close(int fd);
496 extern static int kqueue();
499 extern static int kevent(int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, IntPtr time);