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;
43 enum EventFlags : ushort {
55 SystemFlags = unchecked (0xf000),
62 enum EventFilter : short {
76 enum FilterFlags : uint {
77 ReadPoll = EventFlags.Flag0,
78 ReadOutOfBand = EventFlags.Flag1,
79 ReadLowWaterMark = 0x00000001,
81 WriteLowWaterMark = ReadLowWaterMark,
83 NoteTrigger = 0x01000000,
84 NoteFFNop = 0x00000000,
85 NoteFFAnd = 0x40000000,
86 NoteFFOr = 0x80000000,
87 NoteFFCopy = 0xc0000000,
88 NoteFFCtrlMask = 0xc0000000,
89 NoteFFlagsMask = 0x00ffffff,
91 VNodeDelete = 0x00000001,
92 VNodeWrite = 0x00000002,
93 VNodeExtend = 0x00000004,
94 VNodeAttrib = 0x00000008,
95 VNodeLink = 0x00000010,
96 VNodeRename = 0x00000020,
97 VNodeRevoke = 0x00000040,
98 VNodeNone = 0x00000080,
100 ProcExit = 0x80000000,
101 ProcFork = 0x40000000,
102 ProcExec = 0x20000000,
103 ProcReap = 0x10000000,
104 ProcSignal = 0x08000000,
105 ProcExitStatus = 0x04000000,
106 ProcResourceEnd = 0x02000000,
109 ProcAppactive = 0x00800000,
110 ProcAppBackground = 0x00400000,
111 ProcAppNonUI = 0x00200000,
112 ProcAppInactive = 0x00100000,
113 ProcAppAllStates = 0x00f00000,
116 ProcPDataMask = 0x000fffff,
117 ProcControlMask = 0xfff00000,
119 VMPressure = 0x80000000,
120 VMPressureTerminate = 0x40000000,
121 VMPressureSuddenTerminate = 0x20000000,
122 VMError = 0x10000000,
123 TimerSeconds = 0x00000001,
124 TimerMicroSeconds = 0x00000002,
125 TimerNanoSeconds = 0x00000004,
126 TimerAbsolute = 0x00000008,
129 [StructLayout(LayoutKind.Sequential)]
130 struct kevent : IDisposable {
132 public EventFilter filter;
133 public EventFlags flags;
134 public FilterFlags fflags;
138 public void Dispose ()
140 if (udata != IntPtr.Zero)
141 Marshal.FreeHGlobal (udata);
150 class KeventFileData {
151 public FileSystemInfo fsi;
152 public DateTime LastAccessTime;
153 public DateTime LastWriteTime;
155 public KeventFileData(FileSystemInfo fsi, DateTime LastAccessTime, DateTime LastWriteTime) {
157 this.LastAccessTime = LastAccessTime;
158 this.LastWriteTime = LastWriteTime;
163 public FileSystemWatcher FSW;
165 public string FileMask;
166 public bool IncludeSubdirs;
168 public Hashtable DirEntries;
171 public bool IsDirectory;
174 class KeventWatcher : IFileWatcher
177 static KeventWatcher instance;
178 static Hashtable watches; // <FileSystemWatcher, KeventData>
179 static Thread thread;
183 readonly Dictionary<string, KeventData> filenamesDict = new Dictionary<string, KeventData> ();
184 readonly Dictionary<int, KeventData> fdsDict = new Dictionary<int, KeventData> ();
185 readonly List<int> removeQueue = new List<int> ();
186 readonly List<int> rescanQueue = new List<int> ();
188 private KeventWatcher ()
193 public static bool GetInstance (out IFileWatcher watcher)
195 if (failed == true) {
200 if (instance != null) {
205 watches = Hashtable.Synchronized (new Hashtable ());
213 instance = new KeventWatcher ();
218 public void StartDispatching (FileSystemWatcher fsw)
223 if (thread == null) {
224 thread = new Thread (new ThreadStart (Monitor));
225 thread.IsBackground = true;
229 data = (KeventData) watches [fsw];
233 data = new KeventData ();
235 data.Path = fsw.FullPath;
236 data.FileMask = fsw.MangledFilter;
237 data.IncludeSubdirs = fsw.IncludeSubdirectories;
242 watches [fsw] = data;
248 bool Add (KeventData data, bool postEvents = false)
250 var path = data.Path;
252 if (filenamesDict.ContainsKey (path) || fdsDict.ContainsKey (data.fd) ) {
256 var fd = open (path, 0x8000 /* O_EVTONLY */, 0);
260 filenamesDict.Add (path, data);
261 fdsDict.Add (fd, data);
263 var attrs = File.GetAttributes (data.Path);
264 data.IsDirectory = ((attrs & FileAttributes.Directory) == FileAttributes.Directory);
267 PostEvent (path, data.FSW, FileAction.Added, path);
277 if (!fdsDict.ContainsKey (fd))
280 var data = fdsDict [fd];
282 filenamesDict.Remove (data.Path);
283 removeQueue.Remove (fd);
288 void Remove (string path)
290 var data = filenamesDict [path];
292 filenamesDict.Remove (path);
293 fdsDict.Remove (data.fd);
297 bool Scan (KeventData data, bool postEvents = false)
299 var path = data.Path;
302 if (!data.IncludeSubdirs) {
306 if (data.IsDirectory && !Directory.Exists (path))
309 var attrs = File.GetAttributes (path);
310 if ((attrs & FileAttributes.Directory) == FileAttributes.Directory) {
311 var dirs_to_process = new List<string> ();
312 dirs_to_process.Add (path);
314 while (dirs_to_process.Count > 0) {
315 var tmp_path = dirs_to_process [0];
316 dirs_to_process.RemoveAt (0);
317 var dirinfo = new DirectoryInfo (tmp_path);
318 foreach (var fsi in dirinfo.GetFileSystemInfos ()) {
319 var newdata = new KeventData {
321 FileMask = data.FileMask,
323 IncludeSubdirs = data.IncludeSubdirs
326 if (!Add (newdata, postEvents))
329 var childAttrs = File.GetAttributes (fsi.FullName);
330 if ((childAttrs & FileAttributes.Directory) == FileAttributes.Directory)
331 dirs_to_process.Add (fsi.FullName);
339 public void StopDispatching (FileSystemWatcher fsw)
343 data = (KeventData) watches [fsw];
347 StopMonitoringDirectory (data);
348 watches.Remove (fsw);
349 if (watches.Count == 0)
352 if (!data.IncludeSubdirs)
358 static void StopMonitoringDirectory (KeventData data)
360 close(data.ev.ident);
365 bool firstRun = true;
368 removeQueue.ForEach (Remove);
370 rescanQueue.ForEach (
372 var data = fdsDict[fd];
373 Scan (data, !firstRun);
375 rescanQueue.Remove (fd);
379 foreach (KeventData data in watches.Values) {
383 var changes = new List<kevent> ();
384 var outEvents = new List<kevent> ();
386 foreach (KeyValuePair<int, KeventData> kv in fdsDict) {
387 var change = new kevent {
389 filter = EventFilter.Vnode,
390 flags = EventFlags.Add | EventFlags.Enable | EventFlags.Clear,
391 fflags = FilterFlags.VNodeDelete | FilterFlags.VNodeExtend | FilterFlags.VNodeRename | FilterFlags.VNodeAttrib | FilterFlags.VNodeLink | FilterFlags.VNodeRevoke | FilterFlags.VNodeWrite,
396 changes.Add (change);
397 outEvents.Add (new kevent());
400 if (changes.Count > 0) {
402 var out_array = outEvents.ToArray ();
405 kevent[] changes_array = changes.ToArray ();
406 numEvents = kevent (conn, changes_array, changes_array.Length, out_array, out_array.Length, IntPtr.Zero);
409 for (var i = 0; i < numEvents; i++) {
410 var kevt = out_array [i];
411 if ((kevt.flags & EventFlags.Error) == EventFlags.Error)
412 throw new Exception ("kevent error");
414 if ((kevt.fflags & FilterFlags.VNodeDelete) != 0) {
415 removeQueue.Add (kevt.ident);
416 var data = fdsDict [kevt.ident];
417 PostEvent (data.Path, data.FSW, FileAction.Removed, data.Path);
418 } else if (((kevt.fflags & FilterFlags.VNodeRename) != 0) || ((kevt.fflags & FilterFlags.VNodeRevoke) != 0) || ((kevt.fflags & FilterFlags.VNodeWrite) != 0)) {
419 var data = fdsDict [kevt.ident];
420 if (data.IsDirectory && Directory.Exists (data.Path))
421 rescanQueue.Add (kevt.ident);
423 if ((kevt.fflags & FilterFlags.VNodeRename) != 0) {
424 var newFilename = GetFilenameFromFd (data.fd);
426 PostEvent (data.Path, data.FSW, FileAction.RenamedNewName, data.Path, newFilename);
428 var newEvent = new KeventData {
430 FileMask = data.FileMask,
432 IncludeSubdirs = data.IncludeSubdirs
435 Add (newEvent, false);
437 } else if ((kevt.fflags & FilterFlags.VNodeAttrib) != 0) {
438 var data = fdsDict[kevt.ident];
439 PostEvent (data.Path, data.FSW, FileAction.Modified, data.Path);
455 private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, string fullname, string newname = null)
457 RenamedEventArgs renamed = null;
462 if (fa == FileAction.RenamedNewName)
463 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, "", newname, fullname);
466 fsw.DispatchEvents (fa, filename, ref renamed);
469 System.Threading.Monitor.PulseAll (fsw);
474 private string GetFilenameFromFd (int fd)
476 var sb = new StringBuilder (1024);
478 if (fcntl (fd, 50 /* F_GETPATH */, sb) != -1) {
479 return sb.ToString ();
485 [DllImport("libc", EntryPoint="fcntl", CharSet=CharSet.Auto, SetLastError=true)]
486 public static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb);
489 extern static int open(string path, int flags, int mode_t);
492 extern static int close(int fd);
495 extern static int kqueue();
498 extern static int kevent(int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, IntPtr time);