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