1 /*******************************************************************************
2 
3     File system event which can be registered with the
4     EpollSelectDispatcher. The implementation uses inotify internally, see
5     ocean.sys.Inotify and http://man7.org/linux/man-pages/man7/inotify.7.html
6 
7     Copyright:
8         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
9         All rights reserved.
10 
11     License:
12         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
13         Alternatively, this file may be distributed under the terms of the Tango
14         3-Clause BSD License (see LICENSE_BSD.txt for details).
15 
16 *******************************************************************************/
17 
18 module ocean.io.select.client.FileSystemEvent;
19 
20 
21 
22 
23 
24 import ocean.core.Verify;
25 import ocean.sys.Inotify;
26 import core.stdc.string;
27 import core.sys.linux.sys.inotify;
28 
29 import core.sys.posix.unistd;
30 
31 import ocean.io.select.EpollSelectDispatcher;
32 import ocean.io.select.client.model.ISelectClient: ISelectClient;
33 import ocean.core.Buffer;
34 import ocean.core.SmartUnion;
35 import ocean.meta.types.Qualifiers;
36 
37 
38 /*******************************************************************************
39 
40     Flags to be passed to FileSystemEvent.watch
41 
42 *******************************************************************************/
43 
44 enum FileEventsEnum : uint
45 {
46     /* Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.  */
47     IN_ACCESS           = 0x00000001,   /* File was accessed.  */
48     IN_MODIFY           = 0x00000002,   /* File was modified.  */
49     IN_ATTRIB           = 0x00000004,   /* Metadata changed.  */
50     IN_CLOSE_WRITE      = 0x00000008,   /* Writtable file was closed.  */
51     IN_CLOSE_NOWRITE    = 0x00000010,   /* Unwrittable file was closed.  */
52     IN_CLOSE            = 0x00000018,   /* Close. */
53     IN_OPEN             = 0x00000020,   /* File was opened.  */
54     IN_MOVED_FROM       = 0x00000040,   /* File was moved from X.  */
55     IN_MOVED_TO         = 0x00000080,   /* File was moved to Y.  */
56     IN_MOVE             = 0x000000c0,   /* Moves.  */
57     IN_CREATE           = 0x00000100,   /* Subfile was created.  */
58     IN_DELETE           = 0x00000200,   /* Subfile was deleted.  */
59     IN_DELETE_SELF      = 0x00000400,   /* Self was deleted.  */
60     IN_MOVE_SELF        = 0x00000800,   /* Self was moved.  */
61 
62     /* Events sent by the kernel.  */
63     IN_UMOUNT           = 0x00002000,   /* Backing fs was unmounted.  */
64     IN_Q_OVERFLOW       = 0x00004000,   /* Event queued overflowed  */
65     IN_IGNORED          = 0x00008000,   /* File was ignored  */
66 
67     /* Special flags.  */
68     IN_ONLYDIR          = 0x01000000,   /* Only watch the path if it is a directory.  */
69     IN_DONT_FOLLOW      = 0x02000000,   /* Do not follow a sym link.  */
70     IN_MASK_ADD         = 0x20000000,   /* Add to the mask of an already existing watch.  */
71     IN_ISDIR            = 0x40000000,   /* Event occurred against dir.  */
72     IN_ONESHOT          = 0x80000000,   /* Only send event once.  */
73 
74     IN_ALL_EVENTS       = 0x00000fff,   /* All events which a program can wait on.  */
75 }
76 
77 class FileSystemEvent : ISelectClient
78 {
79     import ocean.core.Buffer;
80 
81     /***************************************************************************
82 
83         Structure carrying only path and event.
84 
85     ***************************************************************************/
86 
87     public struct FileEvent
88     {
89         cstring path;
90         uint event;
91     }
92 
93 
94     /***************************************************************************
95 
96         Structure carrying path, name and event, representing event that happened
97         on a file in a watched directory.
98 
99     ***************************************************************************/
100 
101     public struct DirectoryFileEvent
102     {
103         cstring path;
104         cstring name;
105         uint event;
106     }
107 
108 
109     /***************************************************************************
110 
111         Union of possible events that could happen.
112 
113     ***************************************************************************/
114 
115     public union EventUnion
116     {
117         FileEvent file_event;
118         DirectoryFileEvent directory_file_event;
119     }
120 
121 
122     /***************************************************************************
123 
124         SmartUnion alias for the possible events that could happen.
125 
126     ***************************************************************************/
127 
128     public alias SmartUnion!(EventUnion) RaisedEvent;
129 
130 
131     /***************************************************************************
132 
133         Alias for the notifier delegate that receives the event as a smart union.
134 
135     ***************************************************************************/
136 
137     public alias void delegate ( RaisedEvent ) Notifier;
138 
139 
140     /***************************************************************************
141 
142         Inotify wrapper
143 
144     ***************************************************************************/
145 
146     private Inotify fd;
147 
148 
149     /***************************************************************************
150 
151         Event notifier delegate, specified in the constructor and called whenever
152         a watched file system event fires, passing the SmartUnion describing the
153         event.
154 
155     ***************************************************************************/
156 
157     private Notifier notifier;
158 
159 
160     /***************************************************************************
161 
162         Associative array which maps inotify "watch descriptor" against "path".
163         When a watch is performed in inotify, a new entry is created in this array.
164         On the other hand, every unwatch implies the removal of the entry.
165 
166         Note: The array will never have 2 entries with same path. When same path
167         is provided to watch, the existing "watch descriptor" is re-used
168         See inotify manual for further details.
169 
170     ***************************************************************************/
171 
172     private char[][uint] watched_files;
173 
174 
175     /***********************************************************************
176 
177         Constructor. Creates a custom event and hooks it up to the provided
178         event notifier which in addition accepts the name field.
179 
180         Params:
181             notifier = event notifier
182 
183     ***********************************************************************/
184 
185     public this ( scope Notifier notifier )
186     {
187         this();
188         this.notifier = notifier;
189     }
190 
191 
192     /***************************************************************************
193 
194         Constructor. Initializes a custom event.
195 
196     ***************************************************************************/
197 
198     private this ()
199     {
200         this.fd = new Inotify;
201     }
202 
203 
204     /***********************************************************************
205 
206         Replace the notifier delegate
207 
208         Params:
209             notifier = event notifier
210 
211     ***********************************************************************/
212 
213     public void setNotifier ( scope Notifier notifier )
214     {
215         this.notifier = notifier;
216     }
217 
218 
219     /***********************************************************************
220 
221         Required by ISelectable interface.
222 
223         Returns:
224             file descriptor used to manage custom event
225 
226     ***********************************************************************/
227 
228     public override Handle fileHandle ( )
229     {
230         return this.fd.fileHandle;
231     }
232 
233 
234     /***************************************************************************
235 
236         Returns:
237             the epoll events to register for.
238 
239     ***************************************************************************/
240 
241     public override Event events ( )
242     {
243         return Event.EPOLLIN;
244     }
245 
246 
247     /***************************************************************************
248 
249         Adds or updates the events being watched for the specified path. The
250         handler delegate previously specified will be called when one of the
251         watched events occurs.
252 
253         params:
254             path   = File path to watch (directories are also supported)
255             events = Inotify events that will be watched (flags)
256 
257         Throws:
258             upon failure during addition of new file to watch
259 
260     ***************************************************************************/
261 
262     public void watch ( char[] path, FileEventsEnum events )
263     {
264         //Attention: Existing wd is returned if path is being watched
265         uint wd = this.fd.addWatch(path, events);
266 
267         if ( auto existing_path = wd in this.watched_files )
268         {
269             verify(*existing_path == path);
270         }
271         else
272         {
273             this.watched_files[wd] = path;
274         }
275     }
276 
277 
278     /***************************************************************************
279 
280         Stops watching the specified path. The handler delegate will no longer
281         be called when events on this path occur.
282 
283         Returns:
284             True, if path was successfully removed
285             False, the path was not found in the list of watched paths
286 
287         Throws:
288             upon failure when removing the watch of a file
289 
290     ***************************************************************************/
291 
292     public bool unwatch ( char[] path )
293     {
294         bool removed = false;
295 
296         foreach ( wd, wd_path; this.watched_files )
297         {
298             if ( wd_path == path )
299             {
300                 this.fd.rmWatch(wd);
301                 this.watched_files.remove(wd);
302                 removed = true;
303                 break;
304             }
305         }
306 
307         return removed;
308     }
309 
310 
311     /***************************************************************************
312 
313         Event handler, invoked by the epoll select dispatcher.
314 
315         Params:
316             event = event(s) reported by epoll
317 
318         Returns:
319             true to stay registered in epoll or false to unregister.
320 
321     ***************************************************************************/
322 
323     public override bool handle ( Event event )
324     {
325         foreach ( ev; this.fd.readEvents() )
326         {
327             verify(ev.mask != typeof(ev.mask).init);
328 
329             auto path = ev.wd in this.watched_files;
330             if (path is null)
331                 continue;
332 
333             if (this.notifier)
334             {
335                 RaisedEvent event_info;
336 
337                 if (ev.len > 0)
338                 {
339                     auto name_ptr = cast(char*)&ev.name;
340                     auto name = name_ptr[0..strlen(name_ptr)];
341                     event_info.directory_file_event = DirectoryFileEvent(*path,
342                             name, ev.mask);
343                 }
344                 else
345                 {
346                     event_info.file_event = FileEvent(*path, ev.mask);
347                 }
348 
349                 this.notifier(event_info);
350             }
351         }
352 
353         return true;
354     }
355 
356 }