Port to pedantic memcpy
[emacs.git] / src / inotify.c
blobeddad73e8f748d9dc4f50074868630ffcf64aa9e
1 /* Inotify support for Emacs
3 Copyright (C) 2012-2015 Free Software Foundation, Inc.
5 This file is part of GNU Emacs.
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.
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.
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/>. */
20 #include <config.h>
22 #ifdef HAVE_INOTIFY
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"
32 #include <sys/inotify.h>
33 #include <sys/ioctl.h>
35 /* Ignore bits that might be undefined on old GNU/Linux systems. */
36 #ifndef IN_EXCL_UNLINK
37 # define IN_EXCL_UNLINK 0
38 #endif
39 #ifndef IN_DONT_FOLLOW
40 # define IN_DONT_FOLLOW 0
41 #endif
42 #ifndef IN_ONLYDIR
43 # define IN_ONLYDIR 0
44 #endif
46 /* File handle for inotify. */
47 static int inotifyfd = -1;
49 /* Assoc list of files being watched.
50 Format:
51 (watch-descriptor . callback)
53 static Lisp_Object watch_list;
55 static Lisp_Object
56 make_watch_descriptor (int wd)
58 /* TODO replace this with a Misc Object! */
59 return make_number (wd);
62 static Lisp_Object
63 mask_to_aspects (uint32_t mask) {
64 Lisp_Object aspects = Qnil;
65 if (mask & IN_ACCESS)
66 aspects = Fcons (Qaccess, aspects);
67 if (mask & IN_ATTRIB)
68 aspects = Fcons (Qattrib, aspects);
69 if (mask & IN_CLOSE_WRITE)
70 aspects = Fcons (Qclose_write, aspects);
71 if (mask & IN_CLOSE_NOWRITE)
72 aspects = Fcons (Qclose_nowrite, aspects);
73 if (mask & IN_CREATE)
74 aspects = Fcons (Qcreate, aspects);
75 if (mask & IN_DELETE)
76 aspects = Fcons (Qdelete, aspects);
77 if (mask & IN_DELETE_SELF)
78 aspects = Fcons (Qdelete_self, aspects);
79 if (mask & IN_MODIFY)
80 aspects = Fcons (Qmodify, aspects);
81 if (mask & IN_MOVE_SELF)
82 aspects = Fcons (Qmove_self, aspects);
83 if (mask & IN_MOVED_FROM)
84 aspects = Fcons (Qmoved_from, aspects);
85 if (mask & IN_MOVED_TO)
86 aspects = Fcons (Qmoved_to, aspects);
87 if (mask & IN_OPEN)
88 aspects = Fcons (Qopen, aspects);
89 if (mask & IN_IGNORED)
90 aspects = Fcons (Qignored, aspects);
91 if (mask & IN_ISDIR)
92 aspects = Fcons (Qisdir, aspects);
93 if (mask & IN_Q_OVERFLOW)
94 aspects = Fcons (Qq_overflow, aspects);
95 if (mask & IN_UNMOUNT)
96 aspects = Fcons (Qunmount, aspects);
97 return aspects;
100 static Lisp_Object
101 inotifyevent_to_event (Lisp_Object watch_object, struct inotify_event const *ev)
103 Lisp_Object name = Qnil;
104 if (ev->len > 0)
106 size_t const len = strlen (ev->name);
107 name = make_unibyte_string (ev->name, min (len, ev->len));
108 name = DECODE_FILE (name);
111 return list2 (list4 (make_watch_descriptor (ev->wd),
112 mask_to_aspects (ev->mask),
113 name,
114 make_number (ev->cookie)),
115 XCDR (watch_object));
118 /* This callback is called when the FD is available for read. The inotify
119 events are read from FD and converted into input_events. */
120 static void
121 inotify_callback (int fd, void *_)
123 struct input_event event;
124 Lisp_Object watch_object;
125 int to_read;
126 char *buffer;
127 ssize_t n;
128 size_t i;
130 to_read = 0;
131 if (ioctl (fd, FIONREAD, &to_read) == -1)
132 xsignal1
133 (Qfile_notify_error,
134 build_string ("Error while trying to retrieve file system events"));
135 buffer = xmalloc (to_read);
136 n = read (fd, buffer, to_read);
137 if (n < 0)
139 xfree (buffer);
140 xsignal1
141 (Qfile_notify_error,
142 build_string ("Error while trying to read file system events"));
145 EVENT_INIT (event);
146 event.kind = FILE_NOTIFY_EVENT;
148 i = 0;
149 while (i < (size_t)n)
151 struct inotify_event *ev = (struct inotify_event*)&buffer[i];
153 watch_object = Fassoc (make_watch_descriptor (ev->wd), watch_list);
154 if (!NILP (watch_object))
156 event.arg = inotifyevent_to_event (watch_object, ev);
158 /* If event was removed automatically: Drop it from watch list. */
159 if (ev->mask & IN_IGNORED)
160 watch_list = Fdelete (watch_object, watch_list);
162 if (!NILP (event.arg))
163 kbd_buffer_store_event (&event);
166 i += sizeof (*ev) + ev->len;
169 xfree (buffer);
172 static uint32_t
173 symbol_to_inotifymask (Lisp_Object symb)
175 if (EQ (symb, Qaccess))
176 return IN_ACCESS;
177 else if (EQ (symb, Qattrib))
178 return IN_ATTRIB;
179 else if (EQ (symb, Qclose_write))
180 return IN_CLOSE_WRITE;
181 else if (EQ (symb, Qclose_nowrite))
182 return IN_CLOSE_NOWRITE;
183 else if (EQ (symb, Qcreate))
184 return IN_CREATE;
185 else if (EQ (symb, Qdelete))
186 return IN_DELETE;
187 else if (EQ (symb, Qdelete_self))
188 return IN_DELETE_SELF;
189 else if (EQ (symb, Qmodify))
190 return IN_MODIFY;
191 else if (EQ (symb, Qmove_self))
192 return IN_MOVE_SELF;
193 else if (EQ (symb, Qmoved_from))
194 return IN_MOVED_FROM;
195 else if (EQ (symb, Qmoved_to))
196 return IN_MOVED_TO;
197 else if (EQ (symb, Qopen))
198 return IN_OPEN;
199 else if (EQ (symb, Qmove))
200 return IN_MOVE;
201 else if (EQ (symb, Qclose))
202 return IN_CLOSE;
204 else if (EQ (symb, Qdont_follow))
205 return IN_DONT_FOLLOW;
206 else if (EQ (symb, Qexcl_unlink))
207 return IN_EXCL_UNLINK;
208 else if (EQ (symb, Qmask_add))
209 return IN_MASK_ADD;
210 else if (EQ (symb, Qoneshot))
211 return IN_ONESHOT;
212 else if (EQ (symb, Qonlydir))
213 return IN_ONLYDIR;
215 else if (EQ (symb, Qt) || EQ (symb, Qall_events))
216 return IN_ALL_EVENTS;
217 else
218 xsignal2 (Qfile_notify_error, build_string ("Unknown aspect"), symb);
221 static uint32_t
222 aspect_to_inotifymask (Lisp_Object aspect)
224 if (CONSP (aspect))
226 Lisp_Object x = aspect;
227 uint32_t mask = 0;
228 while (CONSP (x))
230 mask |= symbol_to_inotifymask (XCAR (x));
231 x = XCDR (x);
233 return mask;
235 else
236 return symbol_to_inotifymask (aspect);
239 DEFUN ("inotify-add-watch", Finotify_add_watch, Sinotify_add_watch, 3, 3, 0,
240 doc: /* Add a watch for FILE-NAME to inotify.
242 Return a watch descriptor. The watch will look for ASPECT events and
243 invoke CALLBACK when an event occurs.
245 ASPECT might be one of the following symbols or a list of those symbols:
247 access
248 attrib
249 close-write
250 close-nowrite
251 create
252 delete
253 delete-self
254 modify
255 move-self
256 moved-from
257 moved-to
258 open
260 all-events or t
261 move
262 close
264 The following symbols can also be added to a list of aspects:
266 dont-follow
267 excl-unlink
268 mask-add
269 oneshot
270 onlydir
272 Watching a directory is not recursive. CALLBACK is passed a single argument
273 EVENT which contains an event structure of the format
275 (WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
277 WATCH-DESCRIPTOR is the same object that was returned by this function. It can
278 be tested for equality using `equal'. ASPECTS describes the event. It is a
279 list of ASPECT symbols described above and can also contain one of the following
280 symbols
282 ignored
283 isdir
284 q-overflow
285 unmount
287 If a directory is watched then NAME is the name of file that caused the event.
289 COOKIE is an object that can be compared using `equal' to identify two matching
290 renames (moved-from and moved-to).
292 See inotify(7) and inotify_add_watch(2) for further information. The inotify fd
293 is managed internally and there is no corresponding inotify_init. Use
294 `inotify-rm-watch' to remove a watch.
296 (Lisp_Object file_name, Lisp_Object aspect, Lisp_Object callback)
298 uint32_t mask;
299 Lisp_Object watch_object;
300 Lisp_Object encoded_file_name;
301 Lisp_Object watch_descriptor;
302 int watchdesc = -1;
304 CHECK_STRING (file_name);
306 if (inotifyfd < 0)
308 inotifyfd = inotify_init1 (IN_NONBLOCK|IN_CLOEXEC);
309 if (inotifyfd < 0)
310 xsignal1
311 (Qfile_notify_error,
312 build_string ("File watching feature (inotify) is not available"));
313 watch_list = Qnil;
314 add_read_fd (inotifyfd, &inotify_callback, NULL);
317 mask = aspect_to_inotifymask (aspect);
318 encoded_file_name = ENCODE_FILE (file_name);
319 watchdesc = inotify_add_watch (inotifyfd, SSDATA (encoded_file_name), mask);
320 if (watchdesc == -1)
321 xsignal2 (Qfile_notify_error,
322 build_string ("Could not add watch for file"), file_name);
324 watch_descriptor = make_watch_descriptor (watchdesc);
326 /* Delete existing watch object. */
327 watch_object = Fassoc (watch_descriptor, watch_list);
328 if (!NILP (watch_object))
329 watch_list = Fdelete (watch_object, watch_list);
331 /* Store watch object in watch list. */
332 watch_object = Fcons (watch_descriptor, callback);
333 watch_list = Fcons (watch_object, watch_list);
335 return watch_descriptor;
338 DEFUN ("inotify-rm-watch", Finotify_rm_watch, Sinotify_rm_watch, 1, 1, 0,
339 doc: /* Remove an existing WATCH-DESCRIPTOR.
341 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
343 See inotify_rm_watch(2) for more information.
345 (Lisp_Object watch_descriptor)
347 Lisp_Object watch_object;
348 int wd = XINT (watch_descriptor);
350 if (inotify_rm_watch (inotifyfd, wd) == -1)
351 xsignal2 (Qfile_notify_error,
352 build_string ("Could not rm watch"), watch_descriptor);
354 /* Remove watch descriptor from watch list. */
355 watch_object = Fassoc (watch_descriptor, watch_list);
356 if (!NILP (watch_object))
357 watch_list = Fdelete (watch_object, watch_list);
359 /* Cleanup if no more files are watched. */
360 if (NILP (watch_list))
362 emacs_close (inotifyfd);
363 delete_read_fd (inotifyfd);
364 inotifyfd = -1;
367 return Qt;
370 void
371 syms_of_inotify (void)
373 DEFSYM (Qaccess, "access"); /* IN_ACCESS */
374 DEFSYM (Qattrib, "attrib"); /* IN_ATTRIB */
375 DEFSYM (Qclose_write, "close-write"); /* IN_CLOSE_WRITE */
376 DEFSYM (Qclose_nowrite, "close-nowrite");
377 /* IN_CLOSE_NOWRITE */
378 DEFSYM (Qcreate, "create"); /* IN_CREATE */
379 DEFSYM (Qdelete, "delete"); /* IN_DELETE */
380 DEFSYM (Qdelete_self, "delete-self"); /* IN_DELETE_SELF */
381 DEFSYM (Qmodify, "modify"); /* IN_MODIFY */
382 DEFSYM (Qmove_self, "move-self"); /* IN_MOVE_SELF */
383 DEFSYM (Qmoved_from, "moved-from"); /* IN_MOVED_FROM */
384 DEFSYM (Qmoved_to, "moved-to"); /* IN_MOVED_TO */
385 DEFSYM (Qopen, "open"); /* IN_OPEN */
387 DEFSYM (Qall_events, "all-events"); /* IN_ALL_EVENTS */
388 DEFSYM (Qmove, "move"); /* IN_MOVE */
389 DEFSYM (Qclose, "close"); /* IN_CLOSE */
391 DEFSYM (Qdont_follow, "dont-follow"); /* IN_DONT_FOLLOW */
392 DEFSYM (Qexcl_unlink, "excl-unlink"); /* IN_EXCL_UNLINK */
393 DEFSYM (Qmask_add, "mask-add"); /* IN_MASK_ADD */
394 DEFSYM (Qoneshot, "oneshot"); /* IN_ONESHOT */
395 DEFSYM (Qonlydir, "onlydir"); /* IN_ONLYDIR */
397 DEFSYM (Qignored, "ignored"); /* IN_IGNORED */
398 DEFSYM (Qisdir, "isdir"); /* IN_ISDIR */
399 DEFSYM (Qq_overflow, "q-overflow"); /* IN_Q_OVERFLOW */
400 DEFSYM (Qunmount, "unmount"); /* IN_UNMOUNT */
402 defsubr (&Sinotify_add_watch);
403 defsubr (&Sinotify_rm_watch);
405 staticpro (&watch_list);
407 Fprovide (intern_c_string ("inotify"), Qnil);
410 #endif /* HAVE_INOTIFY */