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