replace XTYPE calls in nextstep files
[bpt/emacs.git] / src / inotify.c
CommitLineData
81606b10
RS
1/* Inotify support for Emacs
2
ba318903 3Copyright (C) 2012-2014 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
81606b10 74/* File handle for inotify. */
c8536ec4 75static int inotifyfd = -1;
81606b10
RS
76
77/* Assoc list of files being watched.
78 Format:
79 (watch-descriptor . callback)
80 */
81static Lisp_Object watch_list;
82
83static Lisp_Object
84make_watch_descriptor (int wd)
85{
86 /* TODO replace this with a Misc Object! */
87 return make_number (wd);
88}
89
90static Lisp_Object
91mask_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
128static Lisp_Object
129inotifyevent_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. */
148static void
149inotify_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
200static uint32_t
201symbol_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
249static uint32_t
250aspect_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
267DEFUN ("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
270Return a watch descriptor. The watch will look for ASPECT events and
271invoke CALLBACK when an event occurs.
272
273ASPECT might be one of the following symbols or a list of those symbols:
81606b10
RS
274
275access
276attrib
277close-write
278close-nowrite
279create
280delete
281delete-self
282modify
283move-self
284moved-from
285moved-to
286open
287
288all-events or t
289move
290close
291
c8536ec4 292The following symbols can also be added to a list of aspects:
81606b10
RS
293
294dont-follow
295excl-unlink
296mask-add
297oneshot
298onlydir
299
c8536ec4
PE
300Watching a directory is not recursive. CALLBACK is passed a single argument
301EVENT which contains an event structure of the format
81606b10 302
86dfb7a8 303(WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
81606b10
RS
304
305WATCH-DESCRIPTOR is the same object that was returned by this function. It can
306be tested for equality using `equal'. ASPECTS describes the event. It is a
307list of ASPECT symbols described above and can also contain one of the following
308symbols
309
310ignored
311isdir
312q-overflow
313unmount
314
86dfb7a8
MA
315If a directory is watched then NAME is the name of file that caused the event.
316
81606b10
RS
317COOKIE is an object that can be compared using `equal' to identify two matching
318renames (moved-from and moved-to).
319
81606b10
RS
320See inotify(7) and inotify_add_watch(2) for further information. The inotify fd
321is 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
366DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0,
367 doc: /* Remove an existing WATCH-DESCRIPTOR.
368
369WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
370
371See 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
398void
399syms_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 */