Commit | Line | Data |
---|---|---|
81606b10 RS |
1 | /* Inotify support for Emacs |
2 | ||
3 | Copyright (C) 2012 | |
4 | Free Software Foundation, Inc. | |
5 | ||
6 | This file is part of GNU Emacs. | |
7 | ||
8 | GNU Emacs is free software: you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation, either version 3 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | GNU Emacs is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */ | |
20 | ||
21 | #include <config.h> | |
22 | ||
23 | #ifdef HAVE_INOTIFY | |
24 | ||
25 | #include "lisp.h" | |
26 | #include "coding.h" | |
27 | #include "process.h" | |
28 | #include "keyboard.h" | |
29 | #include "character.h" | |
30 | #include "frame.h" /* Required for termhooks.h. */ | |
31 | #include "termhooks.h" | |
32 | ||
33 | static Lisp_Object Qaccess; /* IN_ACCESS */ | |
34 | static Lisp_Object Qattrib; /* IN_ATTRIB */ | |
35 | static Lisp_Object Qclose_write; /* IN_CLOSE_WRITE */ | |
36 | static Lisp_Object Qclose_nowrite; /* IN_CLOSE_NOWRITE */ | |
37 | static Lisp_Object Qcreate; /* IN_CREATE */ | |
38 | static Lisp_Object Qdelete; /* IN_DELETE */ | |
39 | static Lisp_Object Qdelete_self; /* IN_DELETE_SELF */ | |
40 | static Lisp_Object Qmodify; /* IN_MODIFY */ | |
41 | static Lisp_Object Qmove_self; /* IN_MOVE_SELF */ | |
42 | static Lisp_Object Qmoved_from; /* IN_MOVED_FROM */ | |
43 | static Lisp_Object Qmoved_to; /* IN_MOVED_TO */ | |
44 | static Lisp_Object Qopen; /* IN_OPEN */ | |
45 | ||
46 | static Lisp_Object Qall_events; /* IN_ALL_EVENTS */ | |
47 | static Lisp_Object Qmove; /* IN_MOVE */ | |
48 | static Lisp_Object Qclose; /* IN_CLOSE */ | |
49 | ||
50 | static Lisp_Object Qdont_follow; /* IN_DONT_FOLLOW */ | |
51 | static Lisp_Object Qexcl_unlink; /* IN_EXCL_UNLINK */ | |
52 | static Lisp_Object Qmask_add; /* IN_MASK_ADD */ | |
53 | static Lisp_Object Qoneshot; /* IN_ONESHOT */ | |
54 | static Lisp_Object Qonlydir; /* IN_ONLYDIR */ | |
55 | ||
56 | static Lisp_Object Qignored; /* IN_IGNORED */ | |
57 | static Lisp_Object Qisdir; /* IN_ISDIR */ | |
58 | static Lisp_Object Qq_overflow; /* IN_Q_OVERFLOW */ | |
59 | static Lisp_Object Qunmount; /* IN_UNMOUNT */ | |
60 | ||
61 | #include <sys/inotify.h> | |
62 | #include <sys/ioctl.h> | |
63 | ||
64 | /* Ignore bits that might be undefined on old GNU/Linux systems. */ | |
65 | #ifndef IN_EXCL_UNLINK | |
66 | # define IN_EXCL_UNLINK 0 | |
67 | #endif | |
68 | #ifndef IN_DONT_FOLLOW | |
69 | # define IN_DONT_FOLLOW 0 | |
70 | #endif | |
71 | #ifndef IN_ONLYDIR | |
72 | # define IN_ONLYDIR 0 | |
73 | #endif | |
74 | ||
75 | enum { uninitialized = -100 }; | |
76 | /* File handle for inotify. */ | |
77 | static int inotifyfd = uninitialized; | |
78 | ||
79 | /* Assoc list of files being watched. | |
80 | Format: | |
81 | (watch-descriptor . callback) | |
82 | */ | |
83 | static Lisp_Object watch_list; | |
84 | ||
85 | static Lisp_Object | |
86 | make_watch_descriptor (int wd) | |
87 | { | |
88 | /* TODO replace this with a Misc Object! */ | |
89 | return make_number (wd); | |
90 | } | |
91 | ||
92 | static Lisp_Object | |
93 | mask_to_aspects (uint32_t mask) { | |
94 | Lisp_Object aspects = Qnil; | |
95 | if (mask & IN_ACCESS) | |
96 | aspects = Fcons (Qaccess, aspects); | |
97 | if (mask & IN_ATTRIB) | |
98 | aspects = Fcons (Qattrib, aspects); | |
99 | if (mask & IN_CLOSE_WRITE) | |
100 | aspects = Fcons (Qclose_write, aspects); | |
101 | if (mask & IN_CLOSE_NOWRITE) | |
102 | aspects = Fcons (Qclose_nowrite, aspects); | |
103 | if (mask & IN_CREATE) | |
104 | aspects = Fcons (Qcreate, aspects); | |
105 | if (mask & IN_DELETE) | |
106 | aspects = Fcons (Qdelete, aspects); | |
107 | if (mask & IN_DELETE_SELF) | |
108 | aspects = Fcons (Qdelete_self, aspects); | |
109 | if (mask & IN_MODIFY) | |
110 | aspects = Fcons (Qmodify, aspects); | |
111 | if (mask & IN_MOVE_SELF) | |
112 | aspects = Fcons (Qmove_self, aspects); | |
113 | if (mask & IN_MOVED_FROM) | |
114 | aspects = Fcons (Qmoved_from, aspects); | |
115 | if (mask & IN_MOVED_TO) | |
116 | aspects = Fcons (Qmoved_to, aspects); | |
117 | if (mask & IN_OPEN) | |
118 | aspects = Fcons (Qopen, aspects); | |
119 | if (mask & IN_IGNORED) | |
120 | aspects = Fcons (Qignored, aspects); | |
121 | if (mask & IN_ISDIR) | |
122 | aspects = Fcons (Qisdir, aspects); | |
123 | if (mask & IN_Q_OVERFLOW) | |
124 | aspects = Fcons (Qq_overflow, aspects); | |
125 | if (mask & IN_UNMOUNT) | |
126 | aspects = Fcons (Qunmount, aspects); | |
127 | return aspects; | |
128 | } | |
129 | ||
130 | static Lisp_Object | |
131 | inotifyevent_to_event (Lisp_Object watch_object, struct inotify_event const *ev) | |
132 | { | |
133 | Lisp_Object name = Qnil; | |
134 | if (ev->len > 0) | |
135 | { | |
136 | size_t const len = strlen (ev->name); | |
137 | name = make_unibyte_string (ev->name, min (len, ev->len)); | |
138 | name = DECODE_FILE (name); | |
139 | } | |
140 | ||
141 | return list2 (list4 (make_watch_descriptor (ev->wd), | |
142 | mask_to_aspects (ev->mask), | |
143 | make_number (ev->cookie), | |
144 | name), | |
145 | XCDR (watch_object)); | |
146 | } | |
147 | ||
148 | /* This callback is called when the FD is available for read. The inotify | |
149 | events are read from FD and converted into input_events. */ | |
150 | static void | |
151 | inotify_callback (int fd, void *_) | |
152 | { | |
153 | struct input_event event; | |
154 | Lisp_Object watch_object; | |
155 | int to_read; | |
156 | char *buffer; | |
157 | ssize_t n; | |
158 | size_t i; | |
159 | ||
160 | to_read = 0; | |
161 | if (ioctl (fd, FIONREAD, &to_read) == -1) | |
162 | report_file_error ("Error while trying to retrieve file system events", | |
163 | Qnil); | |
164 | buffer = xmalloc (to_read); | |
165 | n = read (fd, buffer, to_read); | |
166 | if (n < 0) | |
167 | { | |
168 | xfree (buffer); | |
169 | report_file_error ("Error while trying to read file system events", | |
170 | Qnil); | |
171 | } | |
172 | ||
173 | EVENT_INIT (event); | |
174 | event.kind = FILE_NOTIFY_EVENT; | |
175 | event.arg = Qnil; | |
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); | |
190 | } | |
191 | ||
192 | i += sizeof (*ev) + ev->len; | |
193 | } | |
194 | ||
195 | if (!NILP (event.arg)) | |
196 | kbd_buffer_store_event (&event); | |
197 | ||
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 | |
247 | signal_error ("Unknown aspect", symb); | |
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 | ||
303 | (WATCH-DESCRIPTOR ASPECTS COOKIE NAME) | |
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 | ||
315 | COOKIE is an object that can be compared using `equal' to identify two matching | |
316 | renames (moved-from and moved-to). | |
317 | ||
318 | If a directory is watched then NAME is the name of file that caused the event. | |
319 | ||
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; | |
328 | Lisp_Object decoded_file_name; | |
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; | |
340 | report_file_error ("File watching feature (inotify) is not available", | |
341 | Qnil); | |
342 | } | |
343 | watch_list = Qnil; | |
344 | add_read_fd (inotifyfd, &inotify_callback, NULL); | |
345 | } | |
346 | ||
347 | mask = aspect_to_inotifymask (aspect); | |
348 | decoded_file_name = ENCODE_FILE (file_name); | |
349 | watchdesc = inotify_add_watch (inotifyfd, SSDATA (decoded_file_name), mask); | |
350 | if (watchdesc == -1) | |
351 | report_file_error ("Could not add watch for file", Fcons (file_name, Qnil)); | |
352 | ||
353 | watch_descriptor = make_watch_descriptor (watchdesc); | |
354 | ||
355 | /* Delete existing watch object. */ | |
356 | watch_object = Fassoc (watch_descriptor, watch_list); | |
357 | if (!NILP (watch_object)) | |
358 | watch_list = Fdelete (watch_object, watch_list); | |
359 | ||
360 | /* Store watch object in watch list. */ | |
361 | watch_object = Fcons (watch_descriptor, callback); | |
362 | watch_list = Fcons (watch_object, watch_list); | |
363 | ||
364 | return watch_descriptor; | |
365 | } | |
366 | ||
367 | DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0, | |
368 | doc: /* Remove an existing WATCH-DESCRIPTOR. | |
369 | ||
370 | WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'. | |
371 | ||
372 | See inotify_rm_watch(2) for more information. | |
373 | */) | |
374 | (Lisp_Object watch_descriptor) | |
375 | { | |
376 | Lisp_Object watch_object; | |
377 | int wd = XINT (watch_descriptor); | |
378 | ||
379 | if (inotify_rm_watch (inotifyfd, wd) == -1) | |
380 | report_file_error ("Could not rm watch", Fcons (watch_descriptor, | |
381 | Qnil)); | |
382 | ||
383 | /* Remove watch descriptor from watch list. */ | |
384 | watch_object = Fassoc (watch_descriptor, watch_list); | |
385 | if (!NILP (watch_object)) | |
386 | watch_list = Fdelete (watch_object, watch_list); | |
387 | ||
388 | /* Cleanup if no more files are watched. */ | |
389 | if (NILP (watch_list)) | |
390 | { | |
391 | close (inotifyfd); | |
392 | delete_read_fd (inotifyfd); | |
393 | inotifyfd = uninitialized; | |
394 | } | |
395 | ||
396 | return Qt; | |
397 | } | |
398 | ||
399 | void | |
400 | syms_of_inotify (void) | |
401 | { | |
402 | DEFSYM (Qaccess, "access"); | |
403 | DEFSYM (Qattrib, "attrib"); | |
404 | DEFSYM (Qclose_write, "close-write"); | |
405 | DEFSYM (Qclose_nowrite, "close-nowrite"); | |
406 | DEFSYM (Qcreate, "create"); | |
407 | DEFSYM (Qdelete, "delete"); | |
408 | DEFSYM (Qdelete_self, "delete-self"); | |
409 | DEFSYM (Qmodify, "modify"); | |
410 | DEFSYM (Qmove_self, "move-self"); | |
411 | DEFSYM (Qmoved_from, "moved-from"); | |
412 | DEFSYM (Qmoved_to, "moved-to"); | |
413 | DEFSYM (Qopen, "open"); | |
414 | ||
415 | DEFSYM (Qall_events, "all-events"); | |
416 | DEFSYM (Qmove, "move"); | |
417 | DEFSYM (Qclose, "close"); | |
418 | ||
419 | DEFSYM (Qdont_follow, "dont-follow"); | |
420 | DEFSYM (Qexcl_unlink, "excl-unlink"); | |
421 | DEFSYM (Qmask_add, "mask-add"); | |
422 | DEFSYM (Qoneshot, "oneshot"); | |
423 | DEFSYM (Qonlydir, "onlydir"); | |
424 | ||
425 | DEFSYM (Qignored, "ignored"); | |
426 | DEFSYM (Qisdir, "isdir"); | |
427 | DEFSYM (Qq_overflow, "q-overflow"); | |
428 | DEFSYM (Qunmount, "unmount"); | |
429 | ||
430 | defsubr (&Sinotify_add_watch); | |
431 | defsubr (&Sinotify_rm_watch); | |
432 | ||
433 | staticpro (&watch_list); | |
434 | ||
435 | Fprovide (intern_c_string ("inotify"), Qnil); | |
436 | } | |
437 | ||
438 | #endif /* HAVE_INOTIFY */ |