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 ();
158 StartMonitoringDirectory (data, false);
160 watches [fsw] = data;
161 AppendRequestData (data);
164 } catch {} // ignore the directory if StartMonitoringDirectory fails.
168 static void AppendRequestData (InotifyData data)
171 object obj = requests [wd];
172 ArrayList list = null;
174 requests [data.Watch] = data;
175 } else if (obj is InotifyData) {
176 list = new ArrayList ();
179 requests [data.Watch] = list;
181 list = (ArrayList) obj;
186 static bool RemoveRequestData (InotifyData data)
189 object obj = requests [wd];
193 if (obj is InotifyData) {
195 requests.Remove (wd);
201 ArrayList list = (ArrayList) obj;
203 if (list.Count == 0) {
204 requests.Remove (wd);
210 // Attempt to match MS and linux behavior.
211 static InotifyMask GetMaskFromFilters (NotifyFilters filters)
213 InotifyMask mask = InotifyMask.Create | InotifyMask.Delete | InotifyMask.DeleteSelf | InotifyMask.AddMask;
214 if ((filters & NotifyFilters.Attributes) != 0)
215 mask |= InotifyMask.Attrib;
217 if ((filters & NotifyFilters.Security) != 0)
218 mask |= InotifyMask.Attrib;
220 if ((filters & NotifyFilters.Size) != 0) {
221 mask |= InotifyMask.Attrib;
222 mask |= InotifyMask.Modify;
225 if ((filters & NotifyFilters.LastAccess) != 0) {
226 mask |= InotifyMask.Attrib;
227 mask |= InotifyMask.Access;
228 mask |= InotifyMask.Modify;
229 mask |= InotifyMask.CloseWrite;
232 if ((filters & NotifyFilters.LastWrite) != 0) {
233 mask |= InotifyMask.Attrib;
234 mask |= InotifyMask.CloseWrite;
237 if ((filters & NotifyFilters.FileName) != 0) {
238 mask |= InotifyMask.MovedFrom;
239 mask |= InotifyMask.MovedTo;
242 if ((filters & NotifyFilters.DirectoryName) != 0) {
243 mask |= InotifyMask.MovedFrom;
244 mask |= InotifyMask.MovedTo;
250 static void StartMonitoringDirectory (InotifyData data, bool justcreated)
252 InotifyMask mask = GetMaskFromFilters (data.FSW.NotifyFilter);
253 int wd = AddDirectoryWatch (FD, data.Directory, mask);
255 int error = Marshal.GetLastWin32Error ();
256 if (error == 4) { // Too many open watches
257 string watches = "(unknown)";
259 using (StreamReader reader = new StreamReader ("/proc/sys/fs/inotify/max_user_watches")) {
260 watches = reader.ReadLine ();
264 string msg = String.Format ("The per-user inotify watches limit of {0} has been reached. " +
265 "If you're experiencing problems with your application, increase that limit " +
266 "in /proc/sys/fs/inotify/max_user_watches.", watches);
268 throw new Win32Exception (error, msg);
270 throw new Win32Exception (error);
273 FileSystemWatcher fsw = data.FSW;
276 if (data.IncludeSubdirs) {
277 foreach (string directory in Directory.GetDirectories (data.Directory)) {
278 InotifyData fd = new InotifyData ();
280 fd.Directory = directory;
281 fd.FileMask = data.FSW.MangledFilter;
282 fd.IncludeSubdirs = true;
283 fd.SubDirs = new Hashtable ();
288 RenamedEventArgs renamed = null;
289 fsw.DispatchEvents (FileAction.Added, directory, ref renamed);
292 System.Threading.Monitor.PulseAll (fsw);
298 StartMonitoringDirectory (fd, justcreated);
299 fd.SubDirs [directory] = fd;
300 AppendRequestData (fd);
301 } catch {} // ignore errors and don't add directory.
306 foreach (string filename in Directory.GetFiles (data.Directory)) {
308 RenamedEventArgs renamed = null;
310 fsw.DispatchEvents (FileAction.Added, filename, ref renamed);
311 /* If a file has been created, then it has been written to */
312 fsw.DispatchEvents (FileAction.Modified, filename, ref renamed);
316 System.Threading.Monitor.PulseAll(fsw);
323 public void StopDispatching (FileSystemWatcher fsw)
327 data = (InotifyData) watches [fsw];
331 if (RemoveRequestData (data)) {
332 StopMonitoringDirectory (data);
334 watches.Remove (fsw);
335 if (watches.Count == 0) {
342 if (!data.IncludeSubdirs)
345 foreach (InotifyData idata in data.SubDirs.Values) {
346 if (RemoveRequestData (idata)) {
347 StopMonitoringDirectory (idata);
353 static void StopMonitoringDirectory (InotifyData data)
355 RemoveWatch (FD, data.Watch);
360 byte [] buffer = new byte [4096];
363 nread = ReadFromFD (FD, buffer, (IntPtr) buffer.Length);
368 ProcessEvents (buffer, nread);
379 struct inotify_event {
383 __u32 len; // Includes any trailing null in 'name'
388 static int ReadEvent (byte [] source, int off, int size, out InotifyEvent evt)
390 evt = new InotifyEvent ();
391 if (size <= 0 || off > size - 16) {
396 if (BitConverter.IsLittleEndian) {
397 evt.WatchDescriptor = source [off] + (source [off + 1] << 8) +
398 (source [off + 2] << 16) + (source [off + 3] << 24);
399 evt.Mask = (InotifyMask) (source [off + 4] + (source [off + 5] << 8) +
400 (source [off + 6] << 16) + (source [off + 7] << 24));
401 // Ignore Cookie -> +4
402 len = source [off + 12] + (source [off + 13] << 8) +
403 (source [off + 14] << 16) + (source [off + 15] << 24);
405 evt.WatchDescriptor = source [off + 3] + (source [off + 2] << 8) +
406 (source [off + 1] << 16) + (source [off] << 24);
407 evt.Mask = (InotifyMask) (source [off + 7] + (source [off + 6] << 8) +
408 (source [off + 5] << 16) + (source [off + 4] << 24));
409 // Ignore Cookie -> +4
410 len = source [off + 15] + (source [off + 14] << 8) +
411 (source [off + 13] << 16) + (source [off + 12] << 24);
415 if (off > size - 16 - len)
417 string name = Encoding.UTF8.GetString (source, off + 16, len);
418 evt.Name = name.Trim ('\0');
426 static IEnumerable GetEnumerator (object source)
431 if (source is InotifyData)
434 if (source is ArrayList) {
435 ArrayList list = (ArrayList) source;
436 for (int i = 0; i < list.Count; i++)
437 yield return list [i];
441 /* Interesting events:
451 static InotifyMask Interesting = InotifyMask.Modify | InotifyMask.Attrib | InotifyMask.MovedFrom |
452 InotifyMask.MovedTo | InotifyMask.Create | InotifyMask.Delete |
453 InotifyMask.DeleteSelf | InotifyMask.CloseWrite;
455 void ProcessEvents (byte [] buffer, int length)
457 ArrayList newdirs = null;
460 bool new_name_needed = false;
461 RenamedEventArgs renamed = null;
462 while (length > nread) {
463 int bytes_read = ReadEvent (buffer, nread, length, out evt);
469 InotifyMask mask = evt.Mask;
470 bool is_directory = (mask & InotifyMask.Directory) != 0;
471 mask = (mask & Interesting); // Clear out all the bits that we don't need
475 foreach (InotifyData data in GetEnumerator (requests [evt.WatchDescriptor])) {
476 if (data == null || data.Enabled == false)
479 string directory = data.Directory;
480 string filename = evt.Name;
481 if (filename == null)
482 filename = directory;
484 FileSystemWatcher fsw = data.FSW;
485 FileAction action = 0;
486 if ((mask & (InotifyMask.Modify | InotifyMask.CloseWrite | InotifyMask.Attrib)) != 0) {
487 action = FileAction.Modified;
488 } else if ((mask & InotifyMask.Create) != 0) {
489 action = FileAction.Added;
490 } else if ((mask & InotifyMask.Delete) != 0) {
491 action = FileAction.Removed;
492 } else if ((mask & InotifyMask.DeleteSelf) != 0) {
493 action = FileAction.Removed;
494 } else if ((mask & InotifyMask.MoveSelf) != 0) {
495 //action = FileAction.Removed;
496 continue; // Ignore this one
497 } else if ((mask & InotifyMask.MovedFrom) != 0) {
499 int i = ReadEvent (buffer, nread, length, out to);
500 if (i == -1 || (to.Mask & InotifyMask.MovedTo) == 0) {
501 action = FileAction.Removed;
504 action = FileAction.RenamedNewName;
505 if (evt.Name == data.Directory || fsw.Pattern.IsMatch (evt.Name)) {
506 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, data.Directory, to.Name, evt.Name);
508 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, data.Directory, evt.Name, to.Name);
512 } else if ((mask & InotifyMask.MovedTo) != 0) {
513 action = (new_name_needed) ? FileAction.RenamedNewName : FileAction.Added;
514 new_name_needed = false;
517 if (fsw.IncludeSubdirectories) {
518 string full = fsw.FullPath;
519 string datadir = data.Directory;
520 if (datadir != full) {
521 int len = full.Length;
523 if (len > 1 && full [len - 1] == Path.DirectorySeparatorChar)
525 string reldir = datadir.Substring (full.Length + slash);
526 datadir = Path.Combine (datadir, filename);
527 filename = Path.Combine (reldir, filename);
529 datadir = Path.Combine (full, filename);
532 if (action == FileAction.Added && is_directory) {
534 newdirs = new ArrayList (4);
536 InotifyData fd = new InotifyData ();
538 fd.Directory = datadir;
539 fd.FileMask = fsw.MangledFilter;
540 fd.IncludeSubdirs = true;
541 fd.SubDirs = new Hashtable ();
548 if (filename != data.Directory && !fsw.Pattern.IsMatch (filename)) {
553 fsw.DispatchEvents (action, filename, ref renamed);
554 if (action == FileAction.RenamedNewName)
558 System.Threading.Monitor.PulseAll (fsw);
564 if (newdirs != null) {
565 int count = newdirs.Count;
566 for (int n = 0; n < count; n += 2) {
567 InotifyData newdir = (InotifyData) newdirs [n];
568 InotifyData parent = (InotifyData) newdirs [n + 1];
570 StartMonitoringDirectory (newdir, true);
571 AppendRequestData (newdir);
573 parent.SubDirs [newdir.Directory] = newdir;
575 } catch {} // ignore the given directory
581 static int AddDirectoryWatch (IntPtr fd, string directory, InotifyMask mask)
583 mask |= InotifyMask.Directory;
584 return AddWatch (fd, directory, mask);
587 [DllImport ("libc", EntryPoint="close")]
588 internal extern static int Close (IntPtr fd);
590 [DllImport ("libc", EntryPoint = "read")]
591 extern static int ReadFromFD (IntPtr fd, byte [] buffer, IntPtr length);
593 [MethodImplAttribute(MethodImplOptions.InternalCall)]
594 extern static IntPtr GetInotifyInstance ();
596 [MethodImplAttribute(MethodImplOptions.InternalCall)]
597 extern static int AddWatch (IntPtr fd, string name, InotifyMask mask);
599 [MethodImplAttribute(MethodImplOptions.InternalCall)]
600 extern static IntPtr RemoveWatch (IntPtr fd, int wd);