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
);
371 syms_of_inotify (void)
373 DEFSYM (Qaccess
, "access"); /* IN_ACCESS */
374 DEFSYM (Qattrib
, "attrib"); /* IN_ATTRIB */
375 DEFSYM (Qclose_write
, "close-write"); /* IN_CLOSE_WRITE */
376 DEFSYM (Qclose_nowrite
, "close-nowrite");
377 /* IN_CLOSE_NOWRITE */
378 DEFSYM (Qcreate
, "create"); /* IN_CREATE */
379 DEFSYM (Qdelete
, "delete"); /* IN_DELETE */
380 DEFSYM (Qdelete_self
, "delete-self"); /* IN_DELETE_SELF */
381 DEFSYM (Qmodify
, "modify"); /* IN_MODIFY */
382 DEFSYM (Qmove_self
, "move-self"); /* IN_MOVE_SELF */
383 DEFSYM (Qmoved_from
, "moved-from"); /* IN_MOVED_FROM */
384 DEFSYM (Qmoved_to
, "moved-to"); /* IN_MOVED_TO */
385 DEFSYM (Qopen
, "open"); /* IN_OPEN */
387 DEFSYM (Qall_events
, "all-events"); /* IN_ALL_EVENTS */
388 DEFSYM (Qmove
, "move"); /* IN_MOVE */
389 DEFSYM (Qclose
, "close"); /* IN_CLOSE */
391 DEFSYM (Qdont_follow
, "dont-follow"); /* IN_DONT_FOLLOW */
392 DEFSYM (Qexcl_unlink
, "excl-unlink"); /* IN_EXCL_UNLINK */
393 DEFSYM (Qmask_add
, "mask-add"); /* IN_MASK_ADD */
394 DEFSYM (Qoneshot
, "oneshot"); /* IN_ONESHOT */
395 DEFSYM (Qonlydir
, "onlydir"); /* IN_ONLYDIR */
397 DEFSYM (Qignored
, "ignored"); /* IN_IGNORED */
398 DEFSYM (Qisdir
, "isdir"); /* IN_ISDIR */
399 DEFSYM (Qq_overflow
, "q-overflow"); /* IN_Q_OVERFLOW */
400 DEFSYM (Qunmount
, "unmount"); /* IN_UNMOUNT */
402 defsubr (&Sinotify_add_watch
);
403 defsubr (&Sinotify_rm_watch
);
405 staticpro (&watch_list
);
407 Fprovide (intern_c_string ("inotify"), Qnil
);
410 #endif /* HAVE_INOTIFY */