]> code.delx.au - gnu-emacs/blob - src/inotify.c
Adaot file-notify-tests.el test cases
[gnu-emacs] / src / inotify.c
1 /* Inotify support for Emacs
2
3 Copyright (C) 2012-2015 Free Software Foundation, Inc.
4
5 This file is part of GNU Emacs.
6
7 GNU Emacs is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 GNU Emacs is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
19
20 #include <config.h>
21
22 #ifdef HAVE_INOTIFY
23
24 #include "lisp.h"
25 #include "coding.h"
26 #include "process.h"
27 #include "keyboard.h"
28 #include "character.h"
29 #include "frame.h" /* Required for termhooks.h. */
30 #include "termhooks.h"
31
32 #include <sys/inotify.h>
33 #include <sys/ioctl.h>
34
35 /* Ignore bits that might be undefined on old GNU/Linux systems. */
36 #ifndef IN_EXCL_UNLINK
37 # define IN_EXCL_UNLINK 0
38 #endif
39 #ifndef IN_DONT_FOLLOW
40 # define IN_DONT_FOLLOW 0
41 #endif
42 #ifndef IN_ONLYDIR
43 # define IN_ONLYDIR 0
44 #endif
45
46 /* File handle for inotify. */
47 static int inotifyfd = -1;
48
49 /* Assoc list of files being watched.
50 Format:
51 (watch-descriptor . callback)
52 */
53 static Lisp_Object watch_list;
54
55 static Lisp_Object
56 make_watch_descriptor (int wd)
57 {
58 /* TODO replace this with a Misc Object! */
59 return make_number (wd);
60 }
61
62 static Lisp_Object
63 mask_to_aspects (uint32_t mask) {
64 Lisp_Object aspects = Qnil;
65 if (mask & IN_ACCESS)
66 aspects = Fcons (Qaccess, aspects);
67 if (mask & IN_ATTRIB)
68 aspects = Fcons (Qattrib, aspects);
69 if (mask & IN_CLOSE_WRITE)
70 aspects = Fcons (Qclose_write, aspects);
71 if (mask & IN_CLOSE_NOWRITE)
72 aspects = Fcons (Qclose_nowrite, aspects);
73 if (mask & IN_CREATE)
74 aspects = Fcons (Qcreate, aspects);
75 if (mask & IN_DELETE)
76 aspects = Fcons (Qdelete, aspects);
77 if (mask & IN_DELETE_SELF)
78 aspects = Fcons (Qdelete_self, aspects);
79 if (mask & IN_MODIFY)
80 aspects = Fcons (Qmodify, aspects);
81 if (mask & IN_MOVE_SELF)
82 aspects = Fcons (Qmove_self, aspects);
83 if (mask & IN_MOVED_FROM)
84 aspects = Fcons (Qmoved_from, aspects);
85 if (mask & IN_MOVED_TO)
86 aspects = Fcons (Qmoved_to, aspects);
87 if (mask & IN_OPEN)
88 aspects = Fcons (Qopen, aspects);
89 if (mask & IN_IGNORED)
90 aspects = Fcons (Qignored, aspects);
91 if (mask & IN_ISDIR)
92 aspects = Fcons (Qisdir, aspects);
93 if (mask & IN_Q_OVERFLOW)
94 aspects = Fcons (Qq_overflow, aspects);
95 if (mask & IN_UNMOUNT)
96 aspects = Fcons (Qunmount, aspects);
97 return aspects;
98 }
99
100 static Lisp_Object
101 inotifyevent_to_event (Lisp_Object watch_object, struct inotify_event const *ev)
102 {
103 Lisp_Object name = Qnil;
104 if (ev->len > 0)
105 {
106 size_t const len = strlen (ev->name);
107 name = make_unibyte_string (ev->name, min (len, ev->len));
108 name = DECODE_FILE (name);
109 }
110
111 return list2 (list4 (make_watch_descriptor (ev->wd),
112 mask_to_aspects (ev->mask),
113 name,
114 make_number (ev->cookie)),
115 XCDR (watch_object));
116 }
117
118 /* This callback is called when the FD is available for read. The inotify
119 events are read from FD and converted into input_events. */
120 static void
121 inotify_callback (int fd, void *_)
122 {
123 struct input_event event;
124 Lisp_Object watch_object;
125 int to_read;
126 char *buffer;
127 ssize_t n;
128 size_t i;
129
130 to_read = 0;
131 if (ioctl (fd, FIONREAD, &to_read) == -1)
132 xsignal1
133 (Qfile_notify_error,
134 build_string ("Error while trying to retrieve file system events"));
135 buffer = xmalloc (to_read);
136 n = read (fd, buffer, to_read);
137 if (n < 0)
138 {
139 xfree (buffer);
140 xsignal1
141 (Qfile_notify_error,
142 build_string ("Error while trying to read file system events"));
143 }
144
145 EVENT_INIT (event);
146 event.kind = FILE_NOTIFY_EVENT;
147
148 i = 0;
149 while (i < (size_t)n)
150 {
151 struct inotify_event *ev = (struct inotify_event*)&buffer[i];
152
153 watch_object = Fassoc (make_watch_descriptor (ev->wd), watch_list);
154 if (!NILP (watch_object))
155 {
156 event.arg = inotifyevent_to_event (watch_object, ev);
157
158 /* If event was removed automatically: Drop it from watch list. */
159 if (ev->mask & IN_IGNORED)
160 watch_list = Fdelete (watch_object, watch_list);
161
162 if (!NILP (event.arg))
163 kbd_buffer_store_event (&event);
164 }
165
166 i += sizeof (*ev) + ev->len;
167 }
168
169 xfree (buffer);
170 }
171
172 static uint32_t
173 symbol_to_inotifymask (Lisp_Object symb)
174 {
175 if (EQ (symb, Qaccess))
176 return IN_ACCESS;
177 else if (EQ (symb, Qattrib))
178 return IN_ATTRIB;
179 else if (EQ (symb, Qclose_write))
180 return IN_CLOSE_WRITE;
181 else if (EQ (symb, Qclose_nowrite))
182 return IN_CLOSE_NOWRITE;
183 else if (EQ (symb, Qcreate))
184 return IN_CREATE;
185 else if (EQ (symb, Qdelete))
186 return IN_DELETE;
187 else if (EQ (symb, Qdelete_self))
188 return IN_DELETE_SELF;
189 else if (EQ (symb, Qmodify))
190 return IN_MODIFY;
191 else if (EQ (symb, Qmove_self))
192 return IN_MOVE_SELF;
193 else if (EQ (symb, Qmoved_from))
194 return IN_MOVED_FROM;
195 else if (EQ (symb, Qmoved_to))
196 return IN_MOVED_TO;
197 else if (EQ (symb, Qopen))
198 return IN_OPEN;
199 else if (EQ (symb, Qmove))
200 return IN_MOVE;
201 else if (EQ (symb, Qclose))
202 return IN_CLOSE;
203
204 else if (EQ (symb, Qdont_follow))
205 return IN_DONT_FOLLOW;
206 else if (EQ (symb, Qexcl_unlink))
207 return IN_EXCL_UNLINK;
208 else if (EQ (symb, Qmask_add))
209 return IN_MASK_ADD;
210 else if (EQ (symb, Qoneshot))
211 return IN_ONESHOT;
212 else if (EQ (symb, Qonlydir))
213 return IN_ONLYDIR;
214
215 else if (EQ (symb, Qt) || EQ (symb, Qall_events))
216 return IN_ALL_EVENTS;
217 else
218 xsignal2 (Qfile_notify_error, build_string ("Unknown aspect"), symb);
219 }
220
221 static uint32_t
222 aspect_to_inotifymask (Lisp_Object aspect)
223 {
224 if (CONSP (aspect))
225 {
226 Lisp_Object x = aspect;
227 uint32_t mask = 0;
228 while (CONSP (x))
229 {
230 mask |= symbol_to_inotifymask (XCAR (x));
231 x = XCDR (x);
232 }
233 return mask;
234 }
235 else
236 return symbol_to_inotifymask (aspect);
237 }
238
239 DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0,
240 doc: /* Add a watch for FILE-NAME to inotify.
241
242 Return a watch descriptor. The watch will look for ASPECT events and
243 invoke CALLBACK when an event occurs.
244
245 ASPECT might be one of the following symbols or a list of those symbols:
246
247 access
248 attrib
249 close-write
250 close-nowrite
251 create
252 delete
253 delete-self
254 modify
255 move-self
256 moved-from
257 moved-to
258 open
259
260 all-events or t
261 move
262 close
263
264 The following symbols can also be added to a list of aspects:
265
266 dont-follow
267 excl-unlink
268 mask-add
269 oneshot
270 onlydir
271
272 Watching a directory is not recursive. CALLBACK is passed a single argument
273 EVENT which contains an event structure of the format
274
275 (WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
276
277 WATCH-DESCRIPTOR is the same object that was returned by this function. It can
278 be tested for equality using `equal'. ASPECTS describes the event. It is a
279 list of ASPECT symbols described above and can also contain one of the following
280 symbols
281
282 ignored
283 isdir
284 q-overflow
285 unmount
286
287 If a directory is watched then NAME is the name of file that caused the event.
288
289 COOKIE is an object that can be compared using `equal' to identify two matching
290 renames (moved-from and moved-to).
291
292 See inotify(7) and inotify_add_watch(2) for further information. The inotify fd
293 is managed internally and there is no corresponding inotify_init. Use
294 `inotify-rm-watch' to remove a watch.
295 */)
296 (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback)
297 {
298 uint32_t mask;
299 Lisp_Object watch_object;
300 Lisp_Object encoded_file_name;
301 Lisp_Object watch_descriptor;
302 int watchdesc = -1;
303
304 CHECK_STRING (file_name);
305
306 if (inotifyfd < 0)
307 {
308 inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC);
309 if (inotifyfd < 0)
310 xsignal1
311 (Qfile_notify_error,
312 build_string ("File watching feature (inotify) is not available"));
313 watch_list = Qnil;
314 add_read_fd (inotifyfd, &inotify_callback, NULL);
315 }
316
317 mask = aspect_to_inotifymask (aspect);
318 encoded_file_name = ENCODE_FILE (file_name);
319 watchdesc = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask);
320 if (watchdesc == -1)
321 xsignal2 (Qfile_notify_error,
322 build_string ("Could not add watch for file"), file_name);
323
324 watch_descriptor = make_watch_descriptor (watchdesc);
325
326 /* Delete existing watch object. */
327 watch_object = Fassoc (watch_descriptor, watch_list);
328 if (!NILP (watch_object))
329 watch_list = Fdelete (watch_object, watch_list);
330
331 /* Store watch object in watch list. */
332 watch_object = Fcons (watch_descriptor, callback);
333 watch_list = Fcons (watch_object, watch_list);
334
335 return watch_descriptor;
336 }
337
338 DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0,
339 doc: /* Remove an existing WATCH-DESCRIPTOR.
340
341 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
342
343 See inotify_rm_watch(2) for more information.
344 */)
345 (Lisp_Object watch_descriptor)
346 {
347 Lisp_Object watch_object;
348 int wd = XINT (watch_descriptor);
349
350 if (inotify_rm_watch (inotifyfd, wd) == -1)
351 xsignal2 (Qfile_notify_error,
352 build_string ("Could not rm watch"), watch_descriptor);
353
354 /* Remove watch descriptor from watch list. */
355 watch_object = Fassoc (watch_descriptor, watch_list);
356 if (!NILP (watch_object))
357 watch_list = Fdelete (watch_object, watch_list);
358
359 /* Cleanup if no more files are watched. */
360 if (NILP (watch_list))
361 {
362 emacs_close (inotifyfd);
363 delete_read_fd (inotifyfd);
364 inotifyfd = -1;
365 }
366
367 return Qt;
368 }
369
370 DEFUN ("inotify-valid-p", Finotify_valid_p, Sinotify_valid_p, 1, 1, 0,
371 doc: /* "Check a watch specified by its WATCH-DESCRIPTOR.
372
373 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
374
375 A watch can become invalid if the file or directory it watches is
376 deleted, or if the watcher thread exits abnormally for any other
377 reason. Removing the watch by calling `inotify-rm-watch' also makes
378 it invalid. */)
379 (Lisp_Object watch_descriptor)
380 {
381 Lisp_Object watch_object = Fassoc (watch_descriptor, watch_list);
382 return NILP (watch_object) ? Qnil : Qt;
383 }
384
385 void
386 syms_of_inotify (void)
387 {
388 DEFSYM (Qaccess, "access"); /* IN_ACCESS */
389 DEFSYM (Qattrib, "attrib"); /* IN_ATTRIB */
390 DEFSYM (Qclose_write, "close-write"); /* IN_CLOSE_WRITE */
391 DEFSYM (Qclose_nowrite, "close-nowrite");
392 /* IN_CLOSE_NOWRITE */
393 DEFSYM (Qcreate, "create"); /* IN_CREATE */
394 DEFSYM (Qdelete, "delete"); /* IN_DELETE */
395 DEFSYM (Qdelete_self, "delete-self"); /* IN_DELETE_SELF */
396 DEFSYM (Qmodify, "modify"); /* IN_MODIFY */
397 DEFSYM (Qmove_self, "move-self"); /* IN_MOVE_SELF */
398 DEFSYM (Qmoved_from, "moved-from"); /* IN_MOVED_FROM */
399 DEFSYM (Qmoved_to, "moved-to"); /* IN_MOVED_TO */
400 DEFSYM (Qopen, "open"); /* IN_OPEN */
401
402 DEFSYM (Qall_events, "all-events"); /* IN_ALL_EVENTS */
403 DEFSYM (Qmove, "move"); /* IN_MOVE */
404 DEFSYM (Qclose, "close"); /* IN_CLOSE */
405
406 DEFSYM (Qdont_follow, "dont-follow"); /* IN_DONT_FOLLOW */
407 DEFSYM (Qexcl_unlink, "excl-unlink"); /* IN_EXCL_UNLINK */
408 DEFSYM (Qmask_add, "mask-add"); /* IN_MASK_ADD */
409 DEFSYM (Qoneshot, "oneshot"); /* IN_ONESHOT */
410 DEFSYM (Qonlydir, "onlydir"); /* IN_ONLYDIR */
411
412 DEFSYM (Qignored, "ignored"); /* IN_IGNORED */
413 DEFSYM (Qisdir, "isdir"); /* IN_ISDIR */
414 DEFSYM (Qq_overflow, "q-overflow"); /* IN_Q_OVERFLOW */
415 DEFSYM (Qunmount, "unmount"); /* IN_UNMOUNT */
416
417 defsubr (&Sinotify_add_watch);
418 defsubr (&Sinotify_rm_watch);
419 defsubr (&Sinotify_valid_p);
420
421 staticpro (&watch_list);
422
423 Fprovide (intern_c_string ("inotify"), Qnil);
424 }
425
426 #endif /* HAVE_INOTIFY */