2 // System.IO.Inotify.cs: interface with inotify
5 // Gonzalo Paniagua (gonzalo@novell.com)
6 // Anders Rune Jensen (anders@iola.dk)
8 // (c) 2006 Novell, Inc. (http://www.novell.com)
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.
32 using System.Collections;
33 using System.ComponentModel;
34 using System.Runtime.CompilerServices;
35 using System.Runtime.InteropServices;
37 using System.Threading;
42 enum InotifyMask : uint {
47 CloseNoWrite = 1 << 4,
55 BaseEvents = 0x00000fff,
56 // Can be sent at any time
63 DontFollow = 0x02000000,
65 Directory = 0x40000000,
69 struct InotifyEvent { // Our internal representation for the data returned by the kernel
70 public static readonly InotifyEvent Default = new InotifyEvent ();
71 public int WatchDescriptor;
72 public InotifyMask Mask;
75 public override string ToString ()
77 return String.Format ("[Descriptor: {0} Mask: {1} Name: {2}]", WatchDescriptor, Mask, Name);
81 class ParentInotifyData
83 public bool IncludeSubdirs;
85 public ArrayList children; // InotifyData
86 public InotifyData data;
90 public FileSystemWatcher FSW;
91 public string Directory;
95 class InotifyWatcher : IFileWatcher
98 static InotifyWatcher instance;
99 static Hashtable watches; // FSW to ParentInotifyData
100 static Hashtable requests; // FSW to InotifyData
102 static Thread thread;
105 private InotifyWatcher ()
110 public static bool GetInstance (out IFileWatcher watcher, bool gamin)
112 if (failed == true) {
117 if (instance != null) {
122 FD = GetInotifyInstance ();
123 if ((long) FD == -1) {
129 watches = Hashtable.Synchronized (new Hashtable ());
130 requests = Hashtable.Synchronized (new Hashtable ());
131 instance = new InotifyWatcher ();
136 public void StartDispatching (FileSystemWatcher fsw)
138 ParentInotifyData parent;
141 FD = GetInotifyInstance ();
143 if (thread == null) {
144 thread = new Thread (new ThreadStart (Monitor));
145 thread.IsBackground = true;
149 parent = (ParentInotifyData) watches [fsw];
152 if (parent == null) {
153 InotifyData data = new InotifyData ();
155 data.Directory = fsw.FullPath;
157 parent = new ParentInotifyData();
158 parent.IncludeSubdirs = fsw.IncludeSubdirectories;
159 parent.Enabled = true;
160 parent.children = new ArrayList();
163 watches [fsw] = parent;
166 StartMonitoringDirectory (data, false);
168 AppendRequestData (data);
171 } catch {} // ignore the directory if StartMonitoringDirectory fails.
175 static void AppendRequestData (InotifyData data)
178 object obj = requests [wd];
179 ArrayList list = null;
181 requests [data.Watch] = data;
182 } else if (obj is InotifyData) {
183 list = new ArrayList ();
186 requests [data.Watch] = list;
188 list = (ArrayList) obj;
193 static bool RemoveRequestData (InotifyData data)
196 object obj = requests [wd];
200 if (obj is InotifyData) {
202 requests.Remove (wd);
208 ArrayList list = (ArrayList) obj;
210 if (list.Count == 0) {
211 requests.Remove (wd);
217 // Attempt to match MS and linux behavior.
218 static InotifyMask GetMaskFromFilters (NotifyFilters filters)
220 InotifyMask mask = InotifyMask.Create | InotifyMask.Delete | InotifyMask.DeleteSelf | InotifyMask.AddMask;
221 if ((filters & NotifyFilters.Attributes) != 0)
222 mask |= InotifyMask.Attrib;
224 if ((filters & NotifyFilters.Security) != 0)
225 mask |= InotifyMask.Attrib;
227 if ((filters & NotifyFilters.Size) != 0) {
228 mask |= InotifyMask.Attrib;
229 mask |= InotifyMask.Modify;
232 if ((filters & NotifyFilters.LastAccess) != 0) {
233 mask |= InotifyMask.Attrib;
234 mask |= InotifyMask.Access;
235 mask |= InotifyMask.Modify;
236 mask |= InotifyMask.CloseWrite;
239 if ((filters & NotifyFilters.LastWrite) != 0) {
240 mask |= InotifyMask.Attrib;
241 mask |= InotifyMask.CloseWrite;
244 if ((filters & NotifyFilters.FileName) != 0) {
245 mask |= InotifyMask.MovedFrom;
246 mask |= InotifyMask.MovedTo;
249 if ((filters & NotifyFilters.DirectoryName) != 0) {
250 mask |= InotifyMask.MovedFrom;
251 mask |= InotifyMask.MovedTo;
257 static void StartMonitoringDirectory (InotifyData data, bool justcreated)
259 InotifyMask mask = GetMaskFromFilters (data.FSW.NotifyFilter);
260 int wd = AddDirectoryWatch (FD, data.Directory, mask);
262 int error = Marshal.GetLastWin32Error ();
263 if (error == 4) { // Too many open watches
264 string nr_watches = "(unknown)";
266 using (StreamReader reader = new StreamReader ("/proc/sys/fs/inotify/max_user_watches")) {
267 nr_watches = reader.ReadLine ();
271 string msg = String.Format ("The per-user inotify watches limit of {0} has been reached. " +
272 "If you're experiencing problems with your application, increase that limit " +
273 "in /proc/sys/fs/inotify/max_user_watches.", nr_watches);
275 throw new Win32Exception (error, msg);
277 throw new Win32Exception (error);
280 FileSystemWatcher fsw = data.FSW;
283 ParentInotifyData parent = (ParentInotifyData) watches[fsw];
285 if (parent.IncludeSubdirs) {
286 foreach (string directory in Directory.GetDirectories (data.Directory)) {
287 InotifyData fd = new InotifyData ();
289 fd.Directory = directory;
293 RenamedEventArgs renamed = null;
294 fsw.DispatchEvents (FileAction.Added, directory, ref renamed);
297 System.Threading.Monitor.PulseAll (fsw);
303 StartMonitoringDirectory (fd, justcreated);
304 AppendRequestData (fd);
305 parent.children.Add(fd);
306 } catch {} // ignore errors and don't add directory.
311 foreach (string filename in Directory.GetFiles (data.Directory)) {
313 RenamedEventArgs renamed = null;
315 fsw.DispatchEvents (FileAction.Added, filename, ref renamed);
316 /* If a file has been created, then it has been written to */
317 fsw.DispatchEvents (FileAction.Modified, filename, ref renamed);
321 System.Threading.Monitor.PulseAll(fsw);
328 public void StopDispatching (FileSystemWatcher fsw)
330 ParentInotifyData parent;
332 parent = (ParentInotifyData) watches [fsw];
336 if (RemoveRequestData (parent.data)) {
337 StopMonitoringDirectory (parent.data);
339 watches.Remove (fsw);
340 if (watches.Count == 0) {
347 if (!parent.IncludeSubdirs)
350 foreach (InotifyData idata in parent.children)
352 if (RemoveRequestData (idata)) {
353 StopMonitoringDirectory (idata);
359 static void StopMonitoringDirectory (InotifyData data)
361 RemoveWatch (FD, data.Watch);
366 byte [] buffer = new byte [4096];
369 nread = ReadFromFD (FD, buffer, (IntPtr) buffer.Length);
374 ProcessEvents (buffer, nread);
385 struct inotify_event {
389 __u32 len; // Includes any trailing null in 'name'
394 static int ReadEvent (byte [] source, int off, int size, out InotifyEvent evt)
396 evt = new InotifyEvent ();
397 if (size <= 0 || off > size - 16) {
402 if (BitConverter.IsLittleEndian) {
403 evt.WatchDescriptor = source [off] + (source [off + 1] << 8) +
404 (source [off + 2] << 16) + (source [off + 3] << 24);
405 evt.Mask = (InotifyMask) (source [off + 4] + (source [off + 5] << 8) +
406 (source [off + 6] << 16) + (source [off + 7] << 24));
407 // Ignore Cookie -> +4
408 len = source [off + 12] + (source [off + 13] << 8) +
409 (source [off + 14] << 16) + (source [off + 15] << 24);
411 evt.WatchDescriptor = source [off + 3] + (source [off + 2] << 8) +
412 (source [off + 1] << 16) + (source [off] << 24);
413 evt.Mask = (InotifyMask) (source [off + 7] + (source [off + 6] << 8) +
414 (source [off + 5] << 16) + (source [off + 4] << 24));
415 // Ignore Cookie -> +4
416 len = source [off + 15] + (source [off + 14] << 8) +
417 (source [off + 13] << 16) + (source [off + 12] << 24);
421 if (off > size - 16 - len)
423 string name = Encoding.UTF8.GetString (source, off + 16, len);
424 evt.Name = name.Trim ('\0');
432 static IEnumerable GetEnumerator (object source)
437 if (source is InotifyData)
440 if (source is ArrayList) {
441 ArrayList list = (ArrayList) source;
442 for (int i = 0; i < list.Count; i++)
443 yield return list [i];
447 /* Interesting events:
457 static InotifyMask Interesting = InotifyMask.Modify | InotifyMask.Attrib | InotifyMask.MovedFrom |
458 InotifyMask.MovedTo | InotifyMask.Create | InotifyMask.Delete |
459 InotifyMask.DeleteSelf | InotifyMask.CloseWrite;
461 void ProcessEvents (byte [] buffer, int length)
463 ArrayList newdirs = null;
466 bool new_name_needed = false;
467 RenamedEventArgs renamed = null;
468 while (length > nread) {
469 int bytes_read = ReadEvent (buffer, nread, length, out evt);
475 InotifyMask mask = evt.Mask;
476 bool is_directory = (mask & InotifyMask.Directory) != 0;
477 mask = (mask & Interesting); // Clear out all the bits that we don't need
481 foreach (InotifyData data in GetEnumerator (requests [evt.WatchDescriptor])) {
482 ParentInotifyData parent = (ParentInotifyData) watches[data.FSW];
484 if (data == null || parent.Enabled == false)
487 string directory = data.Directory;
488 string filename = evt.Name;
489 if (filename == null)
490 filename = directory;
492 FileSystemWatcher fsw = data.FSW;
493 FileAction action = 0;
494 if ((mask & (InotifyMask.Modify | InotifyMask.CloseWrite | InotifyMask.Attrib)) != 0) {
495 action = FileAction.Modified;
496 } else if ((mask & InotifyMask.Create) != 0) {
497 action = FileAction.Added;
498 } else if ((mask & InotifyMask.Delete) != 0) {
499 action = FileAction.Removed;
500 } else if ((mask & InotifyMask.DeleteSelf) != 0) {
501 action = FileAction.Removed;
502 } else if ((mask & InotifyMask.MoveSelf) != 0) {
503 //action = FileAction.Removed;
504 continue; // Ignore this one
505 } else if ((mask & InotifyMask.MovedFrom) != 0) {
507 int i = ReadEvent (buffer, nread, length, out to);
508 if (i == -1 || (to.Mask & InotifyMask.MovedTo) == 0) {
509 action = FileAction.Removed;
512 action = FileAction.RenamedNewName;
513 if (evt.Name == data.Directory || fsw.Pattern.IsMatch (evt.Name)) {
514 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, data.Directory, to.Name, evt.Name);
516 renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, data.Directory, evt.Name, to.Name);
520 } else if ((mask & InotifyMask.MovedTo) != 0) {
521 action = (new_name_needed) ? FileAction.RenamedNewName : FileAction.Added;
522 new_name_needed = false;
525 if (fsw.IncludeSubdirectories) {
526 string full = fsw.FullPath;
527 string datadir = data.Directory;
528 if (datadir != full) {
529 int len = full.Length;
531 if (len > 1 && full [len - 1] == Path.DirectorySeparatorChar)
533 string reldir = datadir.Substring (full.Length + slash);
534 datadir = Path.Combine (datadir, filename);
535 filename = Path.Combine (reldir, filename);
537 datadir = Path.Combine (full, filename);
540 if (action == FileAction.Added && is_directory) {
542 newdirs = new ArrayList (2);
544 InotifyData fd = new InotifyData ();
546 fd.Directory = datadir;
551 if (filename != data.Directory && !fsw.Pattern.IsMatch (filename)) {
556 fsw.DispatchEvents (action, filename, ref renamed);
557 if (action == FileAction.RenamedNewName)
561 System.Threading.Monitor.PulseAll (fsw);
567 if (newdirs != null) {
568 foreach (InotifyData newdir in newdirs) {
570 StartMonitoringDirectory (newdir, true);
571 AppendRequestData (newdir);
572 ((ParentInotifyData) watches[newdir.FSW]).children.Add(newdir);
573 } catch {} // ignore the given directory
579 static int AddDirectoryWatch (IntPtr fd, string directory, InotifyMask mask)
581 mask |= InotifyMask.Directory;
582 return AddWatch (fd, directory, mask);
585 [DllImport ("libc", EntryPoint="close")]
586 internal extern static int Close (IntPtr fd);
588 [DllImport ("libc", EntryPoint = "read")]
589 extern static int ReadFromFD (IntPtr fd, byte [] buffer, IntPtr length);
591 [MethodImplAttribute(MethodImplOptions.InternalCall)]
592 extern static IntPtr GetInotifyInstance ();
594 [MethodImplAttribute(MethodImplOptions.InternalCall)]
595 extern static int AddWatch (IntPtr fd, string name, InotifyMask mask);
597 [MethodImplAttribute(MethodImplOptions.InternalCall)]
598 extern static IntPtr RemoveWatch (IntPtr fd, int wd);