2 // System.IO.Inotify.cs: interface with inotify
5 // Gonzalo Paniagua (gonzalo@novell.com)
7 // (c) 2006 Novell, Inc. (http://www.novell.com)
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:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
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.
31 using System.Collections;
32 using System.ComponentModel;
33 using System.Runtime.CompilerServices;
34 using System.Runtime.InteropServices;
36 using System.Threading;
41 enum InotifyMask : uint {
46 CloseNoWrite = 1 << 4,
54 BaseEvents = 0x00000fff,
55 // Can be sent at any time
62 DontFollow = 0x02000000,
64 Directory = 0x40000000,
68 struct InotifyEvent { // Our internal representation for the data returned by the kernel
69 public static readonly InotifyEvent Default = new InotifyEvent ();
70 public int WatchDescriptor;
71 public InotifyMask Mask;
74 public override string ToString ()
76 return String.Format ("[Descriptor: {0} Mask: {1} Name: {2}]", WatchDescriptor, Mask, Name);
81 public FileSystemWatcher FSW;
82 public string Directory;
83 public string FileMask;
84 public bool IncludeSubdirs;
87 public Hashtable SubDirs;
90 class InotifyWatcher : IFileWatcher
93 static InotifyWatcher instance;
94 static Hashtable watches; // FSW to InotifyData
95 static Hashtable requests; // FSW to InotifyData
100 private InotifyWatcher ()
105 public static bool GetInstance (out IFileWatcher watcher, bool gamin)
107 if (failed == true) {
112 if (instance != null) {
117 FD = GetInotifyInstance ();
118 if ((long) FD == -1) {
124 watches = Hashtable.Synchronized (new Hashtable ());
125 requests = Hashtable.Synchronized (new Hashtable ());
126 instance = new InotifyWatcher ();
131 public void StartDispatching (FileSystemWatcher fsw)
136 FD = GetInotifyInstance ();
138 if (thread == null) {
139 thread = new Thread (new ThreadStart (Monitor));
140 thread.IsBackground = true;
144 data = (InotifyData) watches [fsw];
148 data = new InotifyData ();
150 data.Directory = fsw.FullPath;
151 data.FileMask = fsw.MangledFilter;
152 data.IncludeSubdirs = fsw.IncludeSubdirectories;
153 if (data.IncludeSubdirs)
154 data.SubDirs = new Hashtable ();
157 StartMonitoringDirectory (data, false);
159 watches [fsw] = data;
160 AppendRequestData (data);
166 static void AppendRequestData (InotifyData data)
169 object obj = requests [wd];
170 ArrayList list = null;
172 requests [data.Watch] = data;
173 } else if (obj is InotifyData) {
174 list = new ArrayList ();
177 requests [data.Watch] = list;
179 list = (ArrayList) obj;
184 static bool RemoveRequestData (InotifyData data)
187 object obj = requests [wd];
191 if (obj is InotifyData) {
193 requests.Remove (wd);
199 ArrayList list = (ArrayList) obj;
201 if (list.Count == 0) {
202 requests.Remove (wd);
208 // Attempt to match MS and linux behavior.
209 static InotifyMask GetMaskFromFilters (NotifyFilters filters)
211 InotifyMask mask = InotifyMask.Create | InotifyMask.Delete | InotifyMask.DeleteSelf | InotifyMask.AddMask;
212 if ((filters & NotifyFilters.Attributes) != 0)
213 mask |= InotifyMask.Attrib;
215 if ((filters & NotifyFilters.Security) != 0)
216 mask |= InotifyMask.Attrib;
218 if ((filters & NotifyFilters.Size) != 0) {
219 mask |= InotifyMask.Attrib;
220 mask |= InotifyMask.Modify;
223 if ((filters & NotifyFilters.LastAccess) != 0) {
224 mask |= InotifyMask.Attrib;
225 mask |= InotifyMask.Access;
226 mask |= InotifyMask.Modify;
227 mask |= InotifyMask.CloseWrite;
230 if ((filters & NotifyFilters.LastWrite) != 0) {
231 mask |= InotifyMask.Attrib;
232 mask |= InotifyMask.CloseWrite;
235 if ((filters & NotifyFilters.FileName) != 0) {
236 mask |= InotifyMask.MovedFrom;
237 mask |= InotifyMask.MovedTo;
240 if ((filters & NotifyFilters.DirectoryName) != 0) {
241 mask |= InotifyMask.MovedFrom;
242 mask |= InotifyMask.MovedTo;
248 static void StartMonitoringDirectory (InotifyData data, bool justcreated)
250 InotifyMask mask = GetMaskFromFilters (data.FSW.NotifyFilter);
251 int wd = AddDirectoryWatch (FD, data.Directory, mask);
253 int error = Marshal.GetLastWin32Error ();
254 if (error == 4) { // Too many open watches
255 string watches = "(unknown)";
257 using (StreamReader reader = new StreamReader ("/proc/sys/fs/inotify/max_user_watches")) {
258 watches = reader.ReadLine ();
262 string msg = String.Format ("The per-user inotify watches limit of {0} has been reached. " +
263 "If you're experiencing problems with your application, increase that limit " +
264 "in /proc/sys/fs/inotify/max_user_watches.", watches);
266 throw new Win32Exception (error, msg);
268 throw new Win32Exception (error);
271 FileSystemWatcher fsw = data.FSW;
274 if (data.IncludeSubdirs) {
275 foreach (string directory in Directory.GetDirectories (data.Directory)) {
276 InotifyData fd = new InotifyData ();
278 fd.Directory = directory;
279 fd.FileMask = data.FSW.MangledFilter;
280 fd.IncludeSubdirs = true;
281 fd.SubDirs = new Hashtable ();
286 RenamedEventArgs renamed = null;
287 fsw.DispatchEvents (FileAction.Added, directory, ref renamed);
290 System.Threading.Monitor.PulseAll (fsw);
295 StartMonitoringDirectory (fd, justcreated);
296 fd.SubDirs [directory] = fd;
297 AppendRequestData (fd);
302 foreach (string filename in Directory.GetFiles (data.Directory)) {
304 RenamedEventArgs renamed = null;
306 fsw.DispatchEvents (FileAction.Added, filename, ref renamed);
307 /* If a file has been created, then it has been written to */
308 fsw.DispatchEvents (FileAction.Modified, filename, ref renamed);
312 System.Threading.Monitor.PulseAll(fsw);
319 public void StopDispatching (FileSystemWatcher fsw)
323 data = (InotifyData) watches [fsw];
327 if (RemoveRequestData (data)) {
328 StopMonitoringDirectory (data);
330 watches.Remove (fsw);
331 if (watches.Count == 0) {
338 if (!data.IncludeSubdirs)
341 foreach (InotifyData idata in data.SubDirs.Values) {
342 if (RemoveRequestData (idata)) {
343 StopMonitoringDirectory (idata);
349 static void StopMonitoringDirectory (InotifyData data)
351 RemoveWatch (FD, data.Watch);
356 byte [] buffer = new byte [4096];
359 nread = ReadFromFD (FD, buffer, (IntPtr) buffer.Length);
364 ProcessEvents (buffer, nread);
375 struct inotify_event {
379 __u32 len; // Includes any trailing null in 'name'
384 static int ReadEvent (byte [] source, int off, int size, out InotifyEvent evt)
386 evt = new InotifyEvent ();
387 if (size <= 0 || off > size - 16) {
392 if (BitConverter.IsLittleEndian) {
393 evt.WatchDescriptor = source [off] + (source [off + 1] << 8) +
394 (source [off + 2] << 16) + (source [off + 3] << 24);
395 evt.Mask = (InotifyMask) (source [off + 4] + (source [off + 5] << 8) +
396 (source [off + 6] << 16) + (source [off + 7] << 24));
397 // Ignore Cookie -> +4
398 len = source [off + 12] + (source [off + 13] << 8) +
399 (source [off + 14] << 16) + (source [off + 15] << 24);
401 evt.WatchDescriptor = source [off + 3] + (source [off + 2] << 8) +
402 (source [off + 1] << 16) + (source [off] << 24);
403 evt.Mask = (InotifyMask) (source [off + 7] + (source [off + 6] << 8) +
404 (source [off + 5] << 16) + (source [off + 4] << 24));
405 // Ignore Cookie -> +4
406 len = source [off + 15] + (source [off + 14] << 8) +
407 (source [off + 13] << 16) + (source [off + 12] << 24);
411 if (off > size - 16 - len)
413 string name = Encoding.UTF8.GetString (source, off + 16, len);
414 evt.Name = name.Trim ('\0');
422 static IEnumerable GetEnumerator (object source)
427 if (source is InotifyData)
430 if (source is ArrayList) {
431 ArrayList list = (ArrayList) source;
432 for (int i = 0; i < list.Count; i++)
433 yield return list [i];
437 /* Interesting events:
447 static InotifyMask Interesting = InotifyMask.Modify | InotifyMask.Attrib | InotifyMask.MovedFrom |
448 InotifyMask.MovedTo | InotifyMask.Create | InotifyMask.Delete |
449 InotifyMask.DeleteSelf | InotifyMask.CloseWrite;
451 void ProcessEvents (byte [] buffer, int length)
453 ArrayList newdirs = null;
456 bool new_name_needed = false;
457 RenamedEventArgs renamed = null;
458 while (length > nread) {
459 int bytes_read = ReadEvent (buffer, nread, length, out evt);
465 InotifyMask mask = evt.Mask;
466 bool is_directory = (mask & InotifyMask.Directory) != 0;
467 mask = (mask & Interesting); // Clear out all the bits that we don't need
471 foreach (InotifyData data in GetEnumerator (requests [evt.WatchDescriptor])) {
472 if (data == null || data.Enabled == false)
475 string directory = data.Directory;
476 string filename = evt.Name;
477 if (filename == null)
478 filename = directory;
480 FileSystemWatcher fsw = data.FSW;
481 FileAction action = 0;
482 if ((mask & (InotifyMask.Modify | InotifyMask.CloseWrite | InotifyMask.Attrib)) != 0) {
483 action = FileAction.Modified;
484 } else if ((mask & InotifyMask.Create) != 0) {
485 action = FileAction.Added;
486 } else if ((mask & InotifyMask.Delete) != 0) {
487 action = FileAction.Removed;
488 } else if ((mask & InotifyMask.DeleteSelf) != 0) {
489 action = FileAction.Removed;
490 } else if ((mask & InotifyMask.MoveSelf) != 0) {
491 //action = FileAction.Removed;
492 continue; // Ignore this one
493 } else if ((mask & InotifyMask.MovedFrom) != 0) {
495 int i = ReadEvent (buffer, nread, length, out to);
496 if (i == -1 || (to.Mask & InotifyMask.MovedTo) == 0) {
497 action = FileAction.Removed;
500 action = FileAction.RenamedNewName;
501 if (evt.Name == data.Directory || fsw.Pattern.IsMatch (evt.Name)) {
502 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, data.Directory, to.Name, evt.Name);
504 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, data.Directory, evt.Name, to.Name);
508 } else if ((mask & InotifyMask.MovedTo) != 0) {
509 action = (new_name_needed) ? FileAction.RenamedNewName : FileAction.Added;
510 new_name_needed = false;
513 if (fsw.IncludeSubdirectories) {
514 string full = fsw.FullPath;
515 string datadir = data.Directory;
516 if (datadir != full) {
517 int len = full.Length;
519 if (len > 1 && full [len - 1] == Path.DirectorySeparatorChar)
521 string reldir = datadir.Substring (full.Length + slash);
522 datadir = Path.Combine (datadir, filename);
523 filename = Path.Combine (reldir, filename);
525 datadir = Path.Combine (full, filename);
528 if (action == FileAction.Added && is_directory) {
530 newdirs = new ArrayList (4);
532 InotifyData fd = new InotifyData ();
534 fd.Directory = datadir;
535 fd.FileMask = fsw.MangledFilter;
536 fd.IncludeSubdirs = true;
537 fd.SubDirs = new Hashtable ();
544 if (filename != data.Directory && !fsw.Pattern.IsMatch (filename)) {
549 fsw.DispatchEvents (action, filename, ref renamed);
550 if (action == FileAction.RenamedNewName)
554 System.Threading.Monitor.PulseAll (fsw);
560 if (newdirs != null) {
561 int count = newdirs.Count;
562 for (int n = 0; n < count; n += 2) {
563 InotifyData newdir = (InotifyData) newdirs [n];
564 InotifyData parent = (InotifyData) newdirs [n + 1];
565 StartMonitoringDirectory (newdir, true);
566 AppendRequestData (newdir);
568 parent.SubDirs [newdir.Directory] = newdir;
575 static int AddDirectoryWatch (IntPtr fd, string directory, InotifyMask mask)
577 mask |= InotifyMask.Directory;
578 return AddWatch (fd, directory, mask);
581 [DllImport ("libc", EntryPoint="close")]
582 internal extern static int Close (IntPtr fd);
584 [DllImport ("libc", EntryPoint = "read")]
585 extern static int ReadFromFD (IntPtr fd, byte [] buffer, IntPtr length);
587 [MethodImplAttribute(MethodImplOptions.InternalCall)]
588 extern static IntPtr GetInotifyInstance ();
590 [MethodImplAttribute(MethodImplOptions.InternalCall)]
591 extern static int AddWatch (IntPtr fd, string name, InotifyMask mask);
593 [MethodImplAttribute(MethodImplOptions.InternalCall)]
594 extern static IntPtr RemoveWatch (IntPtr fd, int wd);