* src/inotify.c (Finotify_add_watch): aspect can also be a symbol.
[emacs.git] / src / inotify.c
blob004689bd4b511fe63ee6ed697f25c9e5a280c0f9
1 /* Inotify support for Emacs
3 Copyright (C) 2012-2017 Free Software Foundation, Inc.
5 This file is part of GNU Emacs.
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 (at
10 your option) any later version.
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.
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/>. */
20 #include <config.h>
22 #ifdef HAVE_INOTIFY
24 #include "lisp.h"
25 #include "coding.h"
26 #include "process.h"
27 #include "keyboard.h"
28 #include "termhooks.h"
30 #include <errno.h>
31 #include <sys/inotify.h>
32 #include <sys/ioctl.h>
34 /* Ignore bits that might be undefined on old GNU/Linux systems. */
35 #ifndef IN_EXCL_UNLINK
36 # define IN_EXCL_UNLINK 0
37 #endif
38 #ifndef IN_DONT_FOLLOW
39 # define IN_DONT_FOLLOW 0
40 #endif
41 #ifndef IN_ONLYDIR
42 # define IN_ONLYDIR 0
43 #endif
44 #define INOTIFY_DEFAULT_MASK (IN_ALL_EVENTS|IN_EXCL_UNLINK)
46 /* File handle for inotify. */
47 static int inotifyfd = -1;
49 /* Alist of files being watched. We want the returned descriptor to
50 be unique for every watch, but inotify returns the same descriptor
51 for multiple calls to inotify_add_watch with the same file. In
52 order to solve this problem, we add a ID, uniquely identifying a
53 watch/file combination.
55 For the same reason, we also need to store the watch's mask and we
56 can't allow the following flags to be used.
58 IN_EXCL_UNLINK
59 IN_MASK_ADD
60 IN_ONESHOT
61 IN_ONLYDIR
63 Format: (descriptor . ((id filename callback mask) ...))
65 static Lisp_Object watch_list;
67 static Lisp_Object
68 mask_to_aspects (uint32_t mask) {
69 Lisp_Object aspects = Qnil;
70 if (mask & IN_ACCESS)
71 aspects = Fcons (Qaccess, aspects);
72 if (mask & IN_ATTRIB)
73 aspects = Fcons (Qattrib, aspects);
74 if (mask & IN_CLOSE_WRITE)
75 aspects = Fcons (Qclose_write, aspects);
76 if (mask & IN_CLOSE_NOWRITE)
77 aspects = Fcons (Qclose_nowrite, aspects);
78 if (mask & IN_CREATE)
79 aspects = Fcons (Qcreate, aspects);
80 if (mask & IN_DELETE)
81 aspects = Fcons (Qdelete, aspects);
82 if (mask & IN_DELETE_SELF)
83 aspects = Fcons (Qdelete_self, aspects);
84 if (mask & IN_MODIFY)
85 aspects = Fcons (Qmodify, aspects);
86 if (mask & IN_MOVE_SELF)
87 aspects = Fcons (Qmove_self, aspects);
88 if (mask & IN_MOVED_FROM)
89 aspects = Fcons (Qmoved_from, aspects);
90 if (mask & IN_MOVED_TO)
91 aspects = Fcons (Qmoved_to, aspects);
92 if (mask & IN_OPEN)
93 aspects = Fcons (Qopen, aspects);
94 if (mask & IN_IGNORED)
95 aspects = Fcons (Qignored, aspects);
96 if (mask & IN_ISDIR)
97 aspects = Fcons (Qisdir, aspects);
98 if (mask & IN_Q_OVERFLOW)
99 aspects = Fcons (Qq_overflow, aspects);
100 if (mask & IN_UNMOUNT)
101 aspects = Fcons (Qunmount, aspects);
102 return aspects;
105 static uint32_t
106 symbol_to_inotifymask (Lisp_Object symb)
108 if (EQ (symb, Qaccess))
109 return IN_ACCESS;
110 else if (EQ (symb, Qattrib))
111 return IN_ATTRIB;
112 else if (EQ (symb, Qclose_write))
113 return IN_CLOSE_WRITE;
114 else if (EQ (symb, Qclose_nowrite))
115 return IN_CLOSE_NOWRITE;
116 else if (EQ (symb, Qcreate))
117 return IN_CREATE;
118 else if (EQ (symb, Qdelete))
119 return IN_DELETE;
120 else if (EQ (symb, Qdelete_self))
121 return IN_DELETE_SELF;
122 else if (EQ (symb, Qmodify))
123 return IN_MODIFY;
124 else if (EQ (symb, Qmove_self))
125 return IN_MOVE_SELF;
126 else if (EQ (symb, Qmoved_from))
127 return IN_MOVED_FROM;
128 else if (EQ (symb, Qmoved_to))
129 return IN_MOVED_TO;
130 else if (EQ (symb, Qopen))
131 return IN_OPEN;
132 else if (EQ (symb, Qmove))
133 return IN_MOVE;
134 else if (EQ (symb, Qclose))
135 return IN_CLOSE;
137 else if (EQ (symb, Qdont_follow))
138 return IN_DONT_FOLLOW;
140 else if (EQ (symb, Qt) || EQ (symb, Qall_events))
141 return IN_ALL_EVENTS;
142 else
144 errno = EINVAL;
145 report_file_notify_error ("Unknown aspect", symb);
149 static uint32_t
150 aspect_to_inotifymask (Lisp_Object aspect)
152 if (CONSP (aspect))
154 Lisp_Object x = aspect;
155 uint32_t mask = 0;
156 while (CONSP (x))
158 mask |= symbol_to_inotifymask (XCAR (x));
159 x = XCDR (x);
161 return mask;
163 else
164 return symbol_to_inotifymask (aspect);
167 static Lisp_Object
168 make_lispy_mask (uint32_t mask)
170 return Fcons (make_number (mask & 0xffff),
171 make_number (mask >> 16));
174 static bool
175 lispy_mask_match_p (Lisp_Object mask, uint32_t other)
177 return (XINT (XCAR (mask)) & other)
178 || ((XINT (XCDR (mask)) << 16) & other);
181 static Lisp_Object
182 inotifyevent_to_event (Lisp_Object watch, struct inotify_event const *ev)
184 Lisp_Object name = Qnil;
186 if (! lispy_mask_match_p (Fnth (make_number (3), watch), ev->mask))
187 return Qnil;
189 if (ev->len > 0)
191 size_t const len = strlen (ev->name);
192 name = make_unibyte_string (ev->name, min (len, ev->len));
193 name = DECODE_FILE (name);
195 else
196 name = XCAR (XCDR (watch));
198 return list2 (list4 (Fcons (make_number (ev->wd), XCAR (watch)),
199 mask_to_aspects (ev->mask),
200 name,
201 make_number (ev->cookie)),
202 Fnth (make_number (2), watch));
205 /* Add a new watch to watch-descriptor WD watching FILENAME and using
206 CALLBACK. Returns a cons (DESCRIPTOR . ID) uniquely identifying the
207 new watch. */
208 static Lisp_Object
209 add_watch (int wd, Lisp_Object filename,
210 Lisp_Object aspect, Lisp_Object callback)
212 Lisp_Object descriptor = make_number (wd);
213 Lisp_Object elt = Fassoc (descriptor, watch_list);
214 Lisp_Object watches = Fcdr (elt);
215 Lisp_Object watch, watch_id;
216 Lisp_Object mask = make_lispy_mask (aspect_to_inotifymask (aspect));
218 int id = 0;
220 while (! NILP (watches))
222 id = max (id, 1 + XINT (XCAR (XCAR (watches))));
223 watches = XCDR (watches);
226 watch_id = make_number (id);
227 watch = list4 (watch_id, filename, callback, mask);
229 if (NILP (elt))
230 watch_list = Fcons (Fcons (descriptor, Fcons (watch, Qnil)),
231 watch_list);
232 else
233 XSETCDR (elt, Fcons (watch, XCDR (elt)));
235 return Fcons (descriptor, watch_id);
238 /* Remove all watches associated with descriptor. If INVALID_P is
239 true, the descriptor is already invalid, i.e. it received a
240 IN_IGNORED event. In this case skip calling inotify_rm_watch. */
241 static void
242 remove_descriptor (Lisp_Object descriptor, bool invalid_p)
244 Lisp_Object elt = Fassoc (descriptor, watch_list);
246 if (! NILP (elt))
248 int wd = XINT (descriptor);
250 watch_list = Fdelete (elt, watch_list);
251 if (! invalid_p)
252 if (inotify_rm_watch (inotifyfd, wd) == -1)
253 report_file_notify_error ("Could not rm watch", descriptor);
255 /* Cleanup if no more files are watched. */
256 if (NILP (watch_list))
258 emacs_close (inotifyfd);
259 delete_read_fd (inotifyfd);
260 inotifyfd = -1;
264 /* Remove watch associated with (descriptor, id). */
265 static void
266 remove_watch (Lisp_Object descriptor, Lisp_Object id)
268 Lisp_Object elt = Fassoc (descriptor, watch_list);
270 if (! NILP (elt))
272 Lisp_Object watch = Fassoc (id, XCDR (elt));
274 if (! NILP (watch))
275 XSETCDR (elt, Fdelete (watch, XCDR (elt)));
277 /* Remove the descriptor if noone is watching it. */
278 if (NILP (XCDR (elt)))
279 remove_descriptor (descriptor, false);
283 /* This callback is called when the FD is available for read. The inotify
284 events are read from FD and converted into input_events. */
285 static void
286 inotify_callback (int fd, void *_)
288 struct input_event event;
289 int to_read;
290 char *buffer;
291 ssize_t n;
292 size_t i;
294 to_read = 0;
295 if (ioctl (fd, FIONREAD, &to_read) == -1)
296 report_file_notify_error ("Error while retrieving file system events",
297 Qnil);
298 buffer = xmalloc (to_read);
299 n = read (fd, buffer, to_read);
300 if (n < 0)
302 xfree (buffer);
303 report_file_notify_error ("Error while reading file system events", Qnil);
306 EVENT_INIT (event);
307 event.kind = FILE_NOTIFY_EVENT;
309 i = 0;
310 while (i < (size_t)n)
312 struct inotify_event *ev = (struct inotify_event *) &buffer[i];
313 Lisp_Object descriptor = make_number (ev->wd);
314 Lisp_Object elt = Fassoc (descriptor, watch_list);
316 if (! NILP (elt))
318 Lisp_Object watches = XCDR (elt);
319 while (! NILP (watches))
321 event.arg = inotifyevent_to_event (XCAR (watches), ev);
322 if (!NILP (event.arg))
323 kbd_buffer_store_event (&event);
324 watches = XCDR (watches);
326 /* If event was removed automatically: Drop it from watch list. */
327 if (ev->mask & IN_IGNORED)
328 remove_descriptor (descriptor, true);
330 i += sizeof (*ev) + ev->len;
333 xfree (buffer);
336 DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0,
337 doc: /* Add a watch for FILE-NAME to inotify.
339 Return a watch descriptor. The watch will look for ASPECT events and
340 invoke CALLBACK when an event occurs.
342 ASPECT might be one of the following symbols or a list of those symbols:
344 access
345 attrib
346 close-write
347 close-nowrite
348 create
349 delete
350 delete-self
351 modify
352 move-self
353 moved-from
354 moved-to
355 open
357 all-events or t
358 move
359 close
361 The following symbols can also be added to a list of aspects:
363 dont-follow
365 Watching a directory is not recursive. CALLBACK is passed a single argument
366 EVENT which contains an event structure of the format
368 \(WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
370 WATCH-DESCRIPTOR is the same object that was returned by this function. It can
371 be tested for equality using `equal'. ASPECTS describes the event. It is a
372 list of ASPECT symbols described above and can also contain one of the following
373 symbols
375 ignored
376 isdir
377 q-overflow
378 unmount
380 If a directory is watched then NAME is the name of file that caused the event.
382 COOKIE is an object that can be compared using `equal' to identify two matching
383 renames (moved-from and moved-to).
385 See inotify(7) and inotify_add_watch(2) for further information. The
386 inotify fd is managed internally and there is no corresponding
387 inotify_init. Use `inotify-rm-watch' to remove a watch.
389 Also note, that the following inotify bit-masks can not be used, due
390 to the fact that descriptors are shared across different callers.
392 IN_EXCL_UNLINK
393 IN_MASK_ADD
394 IN_ONESHOT
395 IN_ONLYDIR */)
396 (Lisp_Object filename, Lisp_Object aspect, Lisp_Object callback)
398 Lisp_Object encoded_file_name;
399 bool dont_follow = (CONSP (aspect)
400 ? ! NILP (Fmemq (Qdont_follow, aspect))
401 : EQ (Qdont_follow, aspect));
402 int wd = -1;
403 uint32_t mask = (INOTIFY_DEFAULT_MASK
404 | (dont_follow ? IN_DONT_FOLLOW : 0));
406 CHECK_STRING (filename);
408 if (inotifyfd < 0)
410 inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC);
411 if (inotifyfd < 0)
412 report_file_notify_error ("File watching is not available", Qnil);
413 watch_list = Qnil;
414 add_read_fd (inotifyfd, &inotify_callback, NULL);
417 encoded_file_name = ENCODE_FILE (filename);
418 wd = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask);
419 if (wd == -1)
420 report_file_notify_error ("Could not add watch for file", filename);
422 return add_watch (wd, filename, aspect, callback);
425 DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0,
426 doc: /* Remove an existing WATCH-DESCRIPTOR.
428 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
430 See inotify_rm_watch(2) for more information. */)
431 (Lisp_Object watch_descriptor)
434 Lisp_Object descriptor, id;
436 if (! (CONSP (watch_descriptor)
437 && INTEGERP (XCAR (watch_descriptor))
438 && INTEGERP (XCDR (watch_descriptor))))
439 report_file_notify_error ("Invalid descriptor ", watch_descriptor);
441 descriptor = XCAR (watch_descriptor);
442 id = XCDR (watch_descriptor);
443 remove_watch (descriptor, id);
445 return Qt;
448 DEFUN ("inotify-valid-p", Finotify_valid_p, Sinotify_valid_p, 1, 1, 0,
449 doc: /* Check a watch specified by its WATCH-DESCRIPTOR.
451 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
453 A watch can become invalid if the file or directory it watches is
454 deleted, or if the watcher thread exits abnormally for any other
455 reason. Removing the watch by calling `inotify-rm-watch' also makes
456 it invalid. */)
457 (Lisp_Object watch_descriptor)
459 Lisp_Object elt, watch;
461 if (! (CONSP (watch_descriptor)
462 && INTEGERP (XCAR (watch_descriptor))
463 && INTEGERP (XCDR (watch_descriptor))))
464 return Qnil;
466 elt = Fassoc (XCAR (watch_descriptor), watch_list);
467 watch = Fassoc (XCDR (watch_descriptor), XCDR (elt));
469 return ! NILP (watch) ? Qt : Qnil;
472 #ifdef INOTIFY_DEBUG
473 DEFUN ("inotify-watch-list", Finotify_watch_list, Sinotify_watch_list, 0, 0, 0,
474 doc: /* Return a copy of the internal watch_list. */)
476 return Fcopy_sequence (watch_list);
479 DEFUN ("inotify-allocated-p", Finotify_allocated_p, Sinotify_allocated_p, 0, 0, 0,
480 doc: /* Return non-nil, if a inotify instance is allocated. */)
482 return inotifyfd < 0 ? Qnil : Qt;
484 #endif
486 void
487 syms_of_inotify (void)
489 DEFSYM (Qaccess, "access"); /* IN_ACCESS */
490 DEFSYM (Qattrib, "attrib"); /* IN_ATTRIB */
491 DEFSYM (Qclose_write, "close-write"); /* IN_CLOSE_WRITE */
492 DEFSYM (Qclose_nowrite, "close-nowrite");
493 /* IN_CLOSE_NOWRITE */
494 DEFSYM (Qcreate, "create"); /* IN_CREATE */
495 DEFSYM (Qdelete, "delete"); /* IN_DELETE */
496 DEFSYM (Qdelete_self, "delete-self"); /* IN_DELETE_SELF */
497 DEFSYM (Qmodify, "modify"); /* IN_MODIFY */
498 DEFSYM (Qmove_self, "move-self"); /* IN_MOVE_SELF */
499 DEFSYM (Qmoved_from, "moved-from"); /* IN_MOVED_FROM */
500 DEFSYM (Qmoved_to, "moved-to"); /* IN_MOVED_TO */
501 DEFSYM (Qopen, "open"); /* IN_OPEN */
503 DEFSYM (Qall_events, "all-events"); /* IN_ALL_EVENTS */
504 DEFSYM (Qmove, "move"); /* IN_MOVE */
505 DEFSYM (Qclose, "close"); /* IN_CLOSE */
507 DEFSYM (Qdont_follow, "dont-follow"); /* IN_DONT_FOLLOW */
509 DEFSYM (Qignored, "ignored"); /* IN_IGNORED */
510 DEFSYM (Qisdir, "isdir"); /* IN_ISDIR */
511 DEFSYM (Qq_overflow, "q-overflow"); /* IN_Q_OVERFLOW */
512 DEFSYM (Qunmount, "unmount"); /* IN_UNMOUNT */
514 defsubr (&Sinotify_add_watch);
515 defsubr (&Sinotify_rm_watch);
516 defsubr (&Sinotify_valid_p);
518 #ifdef INOTIFY_DEBUG
519 defsubr (&Sinotify_watch_list);
520 defsubr (&Sinotify_allocated_p);
521 #endif
522 staticpro (&watch_list);
524 Fprovide (intern_c_string ("inotify"), Qnil);
527 #endif /* HAVE_INOTIFY */