Try again to fix FreeBSD bug re multithreaded memory alloc.
[bpt/emacs.git] / src / inotify.c
CommitLineData
81606b10
RS
1/* Inotify support for Emacs
2
09b8afb6 3Copyright (C) 2012-2013 Free Software Foundation, Inc.
81606b10
RS
4
5This file is part of GNU Emacs.
6
7GNU Emacs is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
11
12GNU Emacs is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along 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
32static Lisp_Object Qaccess; /* IN_ACCESS */
33static Lisp_Object Qattrib; /* IN_ATTRIB */
34static Lisp_Object Qclose_write; /* IN_CLOSE_WRITE */
35static Lisp_Object Qclose_nowrite; /* IN_CLOSE_NOWRITE */
36static Lisp_Object Qcreate; /* IN_CREATE */
37static Lisp_Object Qdelete; /* IN_DELETE */
38static Lisp_Object Qdelete_self; /* IN_DELETE_SELF */
39static Lisp_Object Qmodify; /* IN_MODIFY */
40static Lisp_Object Qmove_self; /* IN_MOVE_SELF */
41static Lisp_Object Qmoved_from; /* IN_MOVED_FROM */
42static Lisp_Object Qmoved_to; /* IN_MOVED_TO */
43static Lisp_Object Qopen; /* IN_OPEN */
44
45static Lisp_Object Qall_events; /* IN_ALL_EVENTS */
46static Lisp_Object Qmove; /* IN_MOVE */
47static Lisp_Object Qclose; /* IN_CLOSE */
48
49static Lisp_Object Qdont_follow; /* IN_DONT_FOLLOW */
50static Lisp_Object Qexcl_unlink; /* IN_EXCL_UNLINK */
51static Lisp_Object Qmask_add; /* IN_MASK_ADD */
52static Lisp_Object Qoneshot; /* IN_ONESHOT */
53static Lisp_Object Qonlydir; /* IN_ONLYDIR */
54
55static Lisp_Object Qignored; /* IN_IGNORED */
56static Lisp_Object Qisdir; /* IN_ISDIR */
57static Lisp_Object Qq_overflow; /* IN_Q_OVERFLOW */
58static 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
74enum { uninitialized = -100 };
75/* File handle for inotify. */
76static int inotifyfd = uninitialized;
77
78/* Assoc list of files being watched.
79 Format:
80 (watch-descriptor . callback)
81 */
82static Lisp_Object watch_list;
83
84static Lisp_Object
85make_watch_descriptor (int wd)
86{
87 /* TODO replace this with a Misc Object! */
88 return make_number (wd);
89}
90
91static Lisp_Object
92mask_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
129static Lisp_Object
130inotifyevent_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. */
149static void
150inotify_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
199static uint32_t
200symbol_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
248static uint32_t
249aspect_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
266DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0,
267 doc: /* Add a watch for FILE-NAME to inotify.
268
269A WATCH-DESCRIPTOR is returned on success. ASPECT might be one of the following
270symbols or a list of those symbols:
271
272access
273attrib
274close-write
275close-nowrite
276create
277delete
278delete-self
279modify
280move-self
281moved-from
282moved-to
283open
284
285all-events or t
286move
287close
288
289The following symbols can also be added to a list of aspects
290
291dont-follow
292excl-unlink
293mask-add
294oneshot
295onlydir
296
297Watching a directory is not recursive. CALLBACK gets called in case of an
298event. It gets passed a single argument EVENT which contains an event structure
299of the format
300
301(WATCH-DESCRIPTOR ASPECTS COOKIE NAME)
302
303WATCH-DESCRIPTOR is the same object that was returned by this function. It can
304be tested for equality using `equal'. ASPECTS describes the event. It is a
305list of ASPECT symbols described above and can also contain one of the following
306symbols
307
308ignored
309isdir
310q-overflow
311unmount
312
313COOKIE is an object that can be compared using `equal' to identify two matching
314renames (moved-from and moved-to).
315
316If a directory is watched then NAME is the name of file that caused the event.
317
318See inotify(7) and inotify_add_watch(2) for further information. The inotify fd
319is 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
365DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0,
366 doc: /* Remove an existing WATCH-DESCRIPTOR.
367
368WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
369
370See 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
397void
398syms_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 */