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), | |
142 | make_number (ev->cookie), | |
143 | name), | |
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) | |
161 | report_file_error ("Error while trying to retrieve file system events", | |
162 | Qnil); | |
163 | buffer = xmalloc (to_read); | |
164 | n = read (fd, buffer, to_read); | |
165 | if (n < 0) | |
166 | { | |
167 | xfree (buffer); | |
168 | report_file_error ("Error while trying to read file system events", | |
169 | Qnil); | |
170 | } | |
171 | ||
172 | EVENT_INIT (event); | |
173 | event.kind = FILE_NOTIFY_EVENT; | |
81606b10 RS |
174 | |
175 | i = 0; | |
176 | while (i < (size_t)n) | |
177 | { | |
178 | struct inotify_event *ev = (struct inotify_event*)&buffer[i]; | |
179 | ||
180 | watch_object = Fassoc (make_watch_descriptor (ev->wd), watch_list); | |
181 | if (!NILP (watch_object)) | |
182 | { | |
183 | event.arg = inotifyevent_to_event (watch_object, ev); | |
184 | ||
185 | /* If event was removed automatically: Drop it from watch list. */ | |
186 | if (ev->mask & IN_IGNORED) | |
187 | watch_list = Fdelete (watch_object, watch_list); | |
38e791fd MA |
188 | |
189 | if (!NILP (event.arg)) | |
190 | kbd_buffer_store_event (&event); | |
81606b10 RS |
191 | } |
192 | ||
193 | i += sizeof (*ev) + ev->len; | |
194 | } | |
195 | ||
81606b10 RS |
196 | xfree (buffer); |
197 | } | |
198 | ||
199 | static uint32_t | |
200 | symbol_to_inotifymask (Lisp_Object symb) | |
201 | { | |
202 | if (EQ (symb, Qaccess)) | |
203 | return IN_ACCESS; | |
204 | else if (EQ (symb, Qattrib)) | |
205 | return IN_ATTRIB; | |
206 | else if (EQ (symb, Qclose_write)) | |
207 | return IN_CLOSE_WRITE; | |
208 | else if (EQ (symb, Qclose_nowrite)) | |
209 | return IN_CLOSE_NOWRITE; | |
210 | else if (EQ (symb, Qcreate)) | |
211 | return IN_CREATE; | |
212 | else if (EQ (symb, Qdelete)) | |
213 | return IN_DELETE; | |
214 | else if (EQ (symb, Qdelete_self)) | |
215 | return IN_DELETE_SELF; | |
216 | else if (EQ (symb, Qmodify)) | |
217 | return IN_MODIFY; | |
218 | else if (EQ (symb, Qmove_self)) | |
219 | return IN_MOVE_SELF; | |
220 | else if (EQ (symb, Qmoved_from)) | |
221 | return IN_MOVED_FROM; | |
222 | else if (EQ (symb, Qmoved_to)) | |
223 | return IN_MOVED_TO; | |
224 | else if (EQ (symb, Qopen)) | |
225 | return IN_OPEN; | |
226 | else if (EQ (symb, Qmove)) | |
227 | return IN_MOVE; | |
228 | else if (EQ (symb, Qclose)) | |
229 | return IN_CLOSE; | |
230 | ||
231 | else if (EQ (symb, Qdont_follow)) | |
232 | return IN_DONT_FOLLOW; | |
233 | else if (EQ (symb, Qexcl_unlink)) | |
234 | return IN_EXCL_UNLINK; | |
235 | else if (EQ (symb, Qmask_add)) | |
236 | return IN_MASK_ADD; | |
237 | else if (EQ (symb, Qoneshot)) | |
238 | return IN_ONESHOT; | |
239 | else if (EQ (symb, Qonlydir)) | |
240 | return IN_ONLYDIR; | |
241 | ||
242 | else if (EQ (symb, Qt) || EQ (symb, Qall_events)) | |
243 | return IN_ALL_EVENTS; | |
244 | else | |
245 | signal_error ("Unknown aspect", symb); | |
246 | } | |
247 | ||
248 | static uint32_t | |
249 | aspect_to_inotifymask (Lisp_Object aspect) | |
250 | { | |
251 | if (CONSP (aspect)) | |
252 | { | |
253 | Lisp_Object x = aspect; | |
254 | uint32_t mask = 0; | |
255 | while (CONSP (x)) | |
256 | { | |
257 | mask |= symbol_to_inotifymask (XCAR (x)); | |
258 | x = XCDR (x); | |
259 | } | |
260 | return mask; | |
261 | } | |
262 | else | |
263 | return symbol_to_inotifymask (aspect); | |
264 | } | |
265 | ||
266 | DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0, | |
267 | doc: /* Add a watch for FILE-NAME to inotify. | |
268 | ||
269 | A WATCH-DESCRIPTOR is returned on success. ASPECT might be one of the following | |
270 | symbols or a list of those symbols: | |
271 | ||
272 | access | |
273 | attrib | |
274 | close-write | |
275 | close-nowrite | |
276 | create | |
277 | delete | |
278 | delete-self | |
279 | modify | |
280 | move-self | |
281 | moved-from | |
282 | moved-to | |
283 | open | |
284 | ||
285 | all-events or t | |
286 | move | |
287 | close | |
288 | ||
289 | The following symbols can also be added to a list of aspects | |
290 | ||
291 | dont-follow | |
292 | excl-unlink | |
293 | mask-add | |
294 | oneshot | |
295 | onlydir | |
296 | ||
297 | Watching a directory is not recursive. CALLBACK gets called in case of an | |
298 | event. It gets passed a single argument EVENT which contains an event structure | |
299 | of the format | |
300 | ||
301 | (WATCH-DESCRIPTOR ASPECTS COOKIE NAME) | |
302 | ||
303 | WATCH-DESCRIPTOR is the same object that was returned by this function. It can | |
304 | be tested for equality using `equal'. ASPECTS describes the event. It is a | |
305 | list of ASPECT symbols described above and can also contain one of the following | |
306 | symbols | |
307 | ||
308 | ignored | |
309 | isdir | |
310 | q-overflow | |
311 | unmount | |
312 | ||
313 | COOKIE is an object that can be compared using `equal' to identify two matching | |
314 | renames (moved-from and moved-to). | |
315 | ||
316 | If a directory is watched then NAME is the name of file that caused the event. | |
317 | ||
318 | See inotify(7) and inotify_add_watch(2) for further information. The inotify fd | |
319 | is managed internally and there is no corresponding inotify_init. Use | |
320 | `inotify-rm-watch' to remove a watch. | |
321 | */) | |
322 | (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback) | |
323 | { | |
324 | uint32_t mask; | |
325 | Lisp_Object watch_object; | |
1b47babd | 326 | Lisp_Object encoded_file_name; |
81606b10 RS |
327 | Lisp_Object watch_descriptor; |
328 | int watchdesc = -1; | |
329 | ||
330 | CHECK_STRING (file_name); | |
331 | ||
332 | if (inotifyfd == uninitialized) | |
333 | { | |
334 | inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC); | |
335 | if (inotifyfd == -1) | |
336 | { | |
337 | inotifyfd = uninitialized; | |
338 | report_file_error ("File watching feature (inotify) is not available", | |
339 | Qnil); | |
340 | } | |
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 RS |
348 | if (watchdesc == -1) |
349 | report_file_error ("Could not add watch for file", Fcons (file_name, Qnil)); | |
350 | ||
351 | watch_descriptor = make_watch_descriptor (watchdesc); | |
352 | ||
353 | /* Delete existing watch object. */ | |
354 | watch_object = Fassoc (watch_descriptor, watch_list); | |
355 | if (!NILP (watch_object)) | |
356 | watch_list = Fdelete (watch_object, watch_list); | |
357 | ||
358 | /* Store watch object in watch list. */ | |
359 | watch_object = Fcons (watch_descriptor, callback); | |
360 | watch_list = Fcons (watch_object, watch_list); | |
361 | ||
362 | return watch_descriptor; | |
363 | } | |
364 | ||
365 | DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0, | |
366 | doc: /* Remove an existing WATCH-DESCRIPTOR. | |
367 | ||
368 | WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'. | |
369 | ||
370 | See inotify_rm_watch(2) for more information. | |
371 | */) | |
372 | (Lisp_Object watch_descriptor) | |
373 | { | |
374 | Lisp_Object watch_object; | |
375 | int wd = XINT (watch_descriptor); | |
376 | ||
377 | if (inotify_rm_watch (inotifyfd, wd) == -1) | |
378 | report_file_error ("Could not rm watch", Fcons (watch_descriptor, | |
379 | Qnil)); | |
380 | ||
381 | /* Remove watch descriptor from watch list. */ | |
382 | watch_object = Fassoc (watch_descriptor, watch_list); | |
383 | if (!NILP (watch_object)) | |
384 | watch_list = Fdelete (watch_object, watch_list); | |
385 | ||
386 | /* Cleanup if no more files are watched. */ | |
387 | if (NILP (watch_list)) | |
388 | { | |
389 | close (inotifyfd); | |
390 | delete_read_fd (inotifyfd); | |
391 | inotifyfd = uninitialized; | |
392 | } | |
393 | ||
394 | return Qt; | |
395 | } | |
396 | ||
397 | void | |
398 | syms_of_inotify (void) | |
399 | { | |
400 | DEFSYM (Qaccess, "access"); | |
401 | DEFSYM (Qattrib, "attrib"); | |
402 | DEFSYM (Qclose_write, "close-write"); | |
403 | DEFSYM (Qclose_nowrite, "close-nowrite"); | |
404 | DEFSYM (Qcreate, "create"); | |
405 | DEFSYM (Qdelete, "delete"); | |
406 | DEFSYM (Qdelete_self, "delete-self"); | |
407 | DEFSYM (Qmodify, "modify"); | |
408 | DEFSYM (Qmove_self, "move-self"); | |
409 | DEFSYM (Qmoved_from, "moved-from"); | |
410 | DEFSYM (Qmoved_to, "moved-to"); | |
411 | DEFSYM (Qopen, "open"); | |
412 | ||
413 | DEFSYM (Qall_events, "all-events"); | |
414 | DEFSYM (Qmove, "move"); | |
415 | DEFSYM (Qclose, "close"); | |
416 | ||
417 | DEFSYM (Qdont_follow, "dont-follow"); | |
418 | DEFSYM (Qexcl_unlink, "excl-unlink"); | |
419 | DEFSYM (Qmask_add, "mask-add"); | |
420 | DEFSYM (Qoneshot, "oneshot"); | |
421 | DEFSYM (Qonlydir, "onlydir"); | |
422 | ||
423 | DEFSYM (Qignored, "ignored"); | |
424 | DEFSYM (Qisdir, "isdir"); | |
425 | DEFSYM (Qq_overflow, "q-overflow"); | |
426 | DEFSYM (Qunmount, "unmount"); | |
427 | ||
428 | defsubr (&Sinotify_add_watch); | |
429 | defsubr (&Sinotify_rm_watch); | |
430 | ||
431 | staticpro (&watch_list); | |
432 | ||
433 | Fprovide (intern_c_string ("inotify"), Qnil); | |
434 | } | |
435 | ||
436 | #endif /* HAVE_INOTIFY */ |