1 /* Inotify support for Emacs
3 Copyright (C) 2012-2015 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
10 (at 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 "character.h"
29 #include "frame.h" /* Required for termhooks.h. */
30 #include "termhooks.h"
32 #include <sys/inotify.h>
33 #include <sys/ioctl.h>
35 /* Ignore bits that might be undefined on old GNU/Linux systems. */
36 #ifndef IN_EXCL_UNLINK
37 # define IN_EXCL_UNLINK 0
39 #ifndef IN_DONT_FOLLOW
40 # define IN_DONT_FOLLOW 0
46 /* File handle for inotify. */
47 static int inotifyfd
= -1;
49 /* Assoc list of files being watched.
51 (watch-descriptor . callback)
53 static Lisp_Object watch_list
;
56 make_watch_descriptor (int wd
)
58 /* TODO replace this with a Misc Object! */
59 return make_number (wd
);
63 mask_to_aspects (uint32_t mask
) {
64 Lisp_Object aspects
= Qnil
;
66 aspects
= Fcons (Qaccess
, aspects
);
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
);
74 aspects
= Fcons (Qcreate
, aspects
);
76 aspects
= Fcons (Qdelete
, aspects
);
77 if (mask
& IN_DELETE_SELF
)
78 aspects
= Fcons (Qdelete_self
, aspects
);
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
);
88 aspects
= Fcons (Qopen
, aspects
);
89 if (mask
& IN_IGNORED
)
90 aspects
= Fcons (Qignored
, aspects
);
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
);
101 inotifyevent_to_event (Lisp_Object watch_object
, struct inotify_event
const *ev
)
103 Lisp_Object name
= Qnil
;
106 size_t const len
= strlen (ev
->name
);
107 name
= make_unibyte_string (ev
->name
, min (len
, ev
->len
));
108 name
= DECODE_FILE (name
);
111 return list2 (list4 (make_watch_descriptor (ev
->wd
),
112 mask_to_aspects (ev
->mask
),
114 make_number (ev
->cookie
)),
115 XCDR (watch_object
));
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. */
121 inotify_callback (int fd
, void *_
)
123 struct input_event event
;
124 Lisp_Object watch_object
;
131 if (ioctl (fd
, FIONREAD
, &to_read
) == -1)
134 build_string ("Error while trying to retrieve file system events"));
135 buffer
= xmalloc (to_read
);
136 n
= read (fd
, buffer
, to_read
);
142 build_string ("Error while trying to read file system events"));
146 event
.kind
= FILE_NOTIFY_EVENT
;
149 while (i
< (size_t)n
)
151 struct inotify_event
*ev
= (struct inotify_event
*)&buffer
[i
];
153 watch_object
= Fassoc (make_watch_descriptor (ev
->wd
), watch_list
);
154 if (!NILP (watch_object
))
156 event
.arg
= inotifyevent_to_event (watch_object
, ev
);
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
);
162 if (!NILP (event
.arg
))
163 kbd_buffer_store_event (&event
);
166 i
+= sizeof (*ev
) + ev
->len
;
173 symbol_to_inotifymask (Lisp_Object symb
)
175 if (EQ (symb
, Qaccess
))
177 else if (EQ (symb
, Qattrib
))
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
))
185 else if (EQ (symb
, Qdelete
))
187 else if (EQ (symb
, Qdelete_self
))
188 return IN_DELETE_SELF
;
189 else if (EQ (symb
, Qmodify
))
191 else if (EQ (symb
, Qmove_self
))
193 else if (EQ (symb
, Qmoved_from
))
194 return IN_MOVED_FROM
;
195 else if (EQ (symb
, Qmoved_to
))
197 else if (EQ (symb
, Qopen
))
199 else if (EQ (symb
, Qmove
))
201 else if (EQ (symb
, Qclose
))
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
))
210 else if (EQ (symb
, Qoneshot
))
212 else if (EQ (symb
, Qonlydir
))
215 else if (EQ (symb
, Qt
) || EQ (symb
, Qall_events
))
216 return IN_ALL_EVENTS
;
218 xsignal2 (Qfile_notify_error
, build_string ("Unknown aspect"), symb
);
222 aspect_to_inotifymask (Lisp_Object aspect
)
226 Lisp_Object x
= aspect
;
230 mask
|= symbol_to_inotifymask (XCAR (x
));
236 return symbol_to_inotifymask (aspect
);
239 DEFUN ("inotify-add-watch", Finotify_add_watch
, Sinotify_add_watch
, 3, 3, 0,
240 doc
: /* Add a watch for FILE-NAME to inotify.
242 Return a watch descriptor. The watch will look for ASPECT events and
243 invoke CALLBACK when an event occurs.
245 ASPECT might be one of the following symbols or a list of those symbols:
264 The following symbols can also be added to a list of aspects:
272 Watching a directory is not recursive. CALLBACK is passed a single argument
273 EVENT which contains an event structure of the format
275 (WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
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
287 If a directory is watched then NAME is the name of file that caused the event.
289 COOKIE is an object that can be compared using `equal' to identify two matching
290 renames (moved-from and moved-to).
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.
296 (Lisp_Object file_name
, Lisp_Object aspect
, Lisp_Object callback
)
299 Lisp_Object watch_object
;
300 Lisp_Object encoded_file_name
;
301 Lisp_Object watch_descriptor
;
304 CHECK_STRING (file_name
);
308 inotifyfd
= inotify_init1 (IN_NONBLOCK
|IN_CLOEXEC
);
312 build_string ("File watching feature (inotify) is not available"));
314 add_read_fd (inotifyfd
, &inotify_callback
, NULL
);
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
);
321 xsignal2 (Qfile_notify_error
,
322 build_string ("Could not add watch for file"), file_name
);
324 watch_descriptor
= make_watch_descriptor (watchdesc
);
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
);
331 /* Store watch object in watch list. */
332 watch_object
= Fcons (watch_descriptor
, callback
);
333 watch_list
= Fcons (watch_object
, watch_list
);
335 return watch_descriptor
;
338 DEFUN ("inotify-rm-watch", Finotify_rm_watch
, Sinotify_rm_watch
, 1, 1, 0,
339 doc
: /* Remove an existing WATCH-DESCRIPTOR.
341 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
343 See inotify_rm_watch(2) for more information.
345 (Lisp_Object watch_descriptor
)
347 Lisp_Object watch_object
;
348 int wd
= XINT (watch_descriptor
);
350 if (inotify_rm_watch (inotifyfd
, wd
) == -1)
351 xsignal2 (Qfile_notify_error
,
352 build_string ("Could not rm watch"), watch_descriptor
);
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
);
359 /* Cleanup if no more files are watched. */
360 if (NILP (watch_list
))
362 emacs_close (inotifyfd
);
363 delete_read_fd (inotifyfd
);
370 DEFUN ("inotify-valid-p", Finotify_valid_p
, Sinotify_valid_p
, 1, 1, 0,
371 doc
: /* "Check a watch specified by its WATCH-DESCRIPTOR.
373 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
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
379 (Lisp_Object watch_descriptor
)
381 Lisp_Object watch_object
= Fassoc (watch_descriptor
, watch_list
);
382 return NILP (watch_object
) ? Qnil
: Qt
;
386 syms_of_inotify (void)
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 */
402 DEFSYM (Qall_events
, "all-events"); /* IN_ALL_EVENTS */
403 DEFSYM (Qmove
, "move"); /* IN_MOVE */
404 DEFSYM (Qclose
, "close"); /* IN_CLOSE */
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 */
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 */
417 defsubr (&Sinotify_add_watch
);
418 defsubr (&Sinotify_rm_watch
);
419 defsubr (&Sinotify_valid_p
);
421 staticpro (&watch_list
);
423 Fprovide (intern_c_string ("inotify"), Qnil
);
426 #endif /* HAVE_INOTIFY */