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