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/>. */
28 #include "termhooks.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
38 #ifndef IN_DONT_FOLLOW
39 # define IN_DONT_FOLLOW 0
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.
63 Format: (descriptor . ((id filename callback mask) ...))
65 static Lisp_Object watch_list
;
68 mask_to_aspects (uint32_t mask
) {
69 Lisp_Object aspects
= Qnil
;
71 aspects
= Fcons (Qaccess
, aspects
);
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
);
79 aspects
= Fcons (Qcreate
, aspects
);
81 aspects
= Fcons (Qdelete
, aspects
);
82 if (mask
& IN_DELETE_SELF
)
83 aspects
= Fcons (Qdelete_self
, aspects
);
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
);
93 aspects
= Fcons (Qopen
, aspects
);
94 if (mask
& IN_IGNORED
)
95 aspects
= Fcons (Qignored
, aspects
);
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
);
106 symbol_to_inotifymask (Lisp_Object symb
)
108 if (EQ (symb
, Qaccess
))
110 else if (EQ (symb
, Qattrib
))
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
))
118 else if (EQ (symb
, Qdelete
))
120 else if (EQ (symb
, Qdelete_self
))
121 return IN_DELETE_SELF
;
122 else if (EQ (symb
, Qmodify
))
124 else if (EQ (symb
, Qmove_self
))
126 else if (EQ (symb
, Qmoved_from
))
127 return IN_MOVED_FROM
;
128 else if (EQ (symb
, Qmoved_to
))
130 else if (EQ (symb
, Qopen
))
132 else if (EQ (symb
, Qmove
))
134 else if (EQ (symb
, Qclose
))
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
;
145 report_file_notify_error ("Unknown aspect", symb
);
150 aspect_to_inotifymask (Lisp_Object aspect
)
154 Lisp_Object x
= aspect
;
158 mask
|= symbol_to_inotifymask (XCAR (x
));
164 return symbol_to_inotifymask (aspect
);
168 make_lispy_mask (uint32_t mask
)
170 return Fcons (make_number (mask
& 0xffff),
171 make_number (mask
>> 16));
175 lispy_mask_match_p (Lisp_Object mask
, uint32_t other
)
177 return (XINT (XCAR (mask
)) & other
)
178 || ((XINT (XCDR (mask
)) << 16) & other
);
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
))
191 size_t const len
= strlen (ev
->name
);
192 name
= make_unibyte_string (ev
->name
, min (len
, ev
->len
));
193 name
= DECODE_FILE (name
);
196 name
= XCAR (XCDR (watch
));
198 return list2 (list4 (Fcons (make_number (ev
->wd
), XCAR (watch
)),
199 mask_to_aspects (ev
->mask
),
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
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
));
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
);
230 watch_list
= Fcons (Fcons (descriptor
, Fcons (watch
, Qnil
)),
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. */
242 remove_descriptor (Lisp_Object descriptor
, bool invalid_p
)
244 Lisp_Object elt
= Fassoc (descriptor
, watch_list
);
248 int wd
= XINT (descriptor
);
250 watch_list
= Fdelete (elt
, watch_list
);
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
);
264 /* Remove watch associated with (descriptor, id). */
266 remove_watch (Lisp_Object descriptor
, Lisp_Object id
)
268 Lisp_Object elt
= Fassoc (descriptor
, watch_list
);
272 Lisp_Object watch
= Fassoc (id
, XCDR (elt
));
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. */
286 inotify_callback (int fd
, void *_
)
288 struct input_event event
;
295 if (ioctl (fd
, FIONREAD
, &to_read
) == -1)
296 report_file_notify_error ("Error while retrieving file system events",
298 buffer
= xmalloc (to_read
);
299 n
= read (fd
, buffer
, to_read
);
303 report_file_notify_error ("Error while reading file system events", Qnil
);
307 event
.kind
= FILE_NOTIFY_EVENT
;
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
);
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
;
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:
361 The following symbols can also be added to a list of aspects:
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
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.
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
));
403 uint32_t mask
= (INOTIFY_DEFAULT_MASK
404 | (dont_follow
? IN_DONT_FOLLOW
: 0));
406 CHECK_STRING (filename
);
410 inotifyfd
= inotify_init1 (IN_NONBLOCK
|IN_CLOEXEC
);
412 report_file_notify_error ("File watching is not available", 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
);
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
);
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
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
))))
466 elt
= Fassoc (XCAR (watch_descriptor
), watch_list
);
467 watch
= Fassoc (XCDR (watch_descriptor
), XCDR (elt
));
469 return ! NILP (watch
) ? Qt
: Qnil
;
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
;
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
);
519 defsubr (&Sinotify_watch_list
);
520 defsubr (&Sinotify_allocated_p
);
522 staticpro (&watch_list
);
524 Fprovide (intern_c_string ("inotify"), Qnil
);
527 #endif /* HAVE_INOTIFY */