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/>. */
28 #include "character.h"
29 #include "frame.h" /* Required for termhooks.h. */
30 #include "termhooks.h"
33 #include <sys/inotify.h>
34 #include <sys/ioctl.h>
36 /* Ignore bits that might be undefined on old GNU/Linux systems. */
37 #ifndef IN_EXCL_UNLINK
38 # define IN_EXCL_UNLINK 0
40 #ifndef IN_DONT_FOLLOW
41 # define IN_DONT_FOLLOW 0
47 /* File handle for inotify. */
48 static int inotifyfd
= -1;
50 /* Assoc list of files being watched.
52 (watch-descriptor . callback)
54 static Lisp_Object watch_list
;
57 make_watch_descriptor (int wd
)
59 /* TODO replace this with a Misc Object! */
60 return make_number (wd
);
64 mask_to_aspects (uint32_t mask
) {
65 Lisp_Object aspects
= Qnil
;
67 aspects
= Fcons (Qaccess
, aspects
);
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
);
75 aspects
= Fcons (Qcreate
, aspects
);
77 aspects
= Fcons (Qdelete
, aspects
);
78 if (mask
& IN_DELETE_SELF
)
79 aspects
= Fcons (Qdelete_self
, aspects
);
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
);
89 aspects
= Fcons (Qopen
, aspects
);
90 if (mask
& IN_IGNORED
)
91 aspects
= Fcons (Qignored
, aspects
);
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
);
102 inotifyevent_to_event (Lisp_Object watch_object
, struct inotify_event
const *ev
)
104 Lisp_Object name
= Qnil
;
107 size_t const len
= strlen (ev
->name
);
108 name
= make_unibyte_string (ev
->name
, min (len
, ev
->len
));
109 name
= DECODE_FILE (name
);
112 return list2 (list4 (make_watch_descriptor (ev
->wd
),
113 mask_to_aspects (ev
->mask
),
115 make_number (ev
->cookie
)),
116 XCDR (watch_object
));
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
)
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
);
131 xsignal (Qfile_notify_error
, Fcons (build_string (string
), errdata
));
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. */
137 inotify_callback (int fd
, void *_
)
139 struct input_event event
;
140 Lisp_Object watch_object
;
147 if (ioctl (fd
, FIONREAD
, &to_read
) == -1)
148 report_inotify_error ("Error while trying to retrieve file system events",
150 buffer
= xmalloc (to_read
);
151 n
= read (fd
, buffer
, to_read
);
155 report_inotify_error ("Error while trying to read file system events",
160 event
.kind
= FILE_NOTIFY_EVENT
;
163 while (i
< (size_t)n
)
165 struct inotify_event
*ev
= (struct inotify_event
*)&buffer
[i
];
167 watch_object
= Fassoc (make_watch_descriptor (ev
->wd
), watch_list
);
168 if (!NILP (watch_object
))
170 event
.arg
= inotifyevent_to_event (watch_object
, ev
);
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
);
176 if (!NILP (event
.arg
))
177 kbd_buffer_store_event (&event
);
180 i
+= sizeof (*ev
) + ev
->len
;
187 symbol_to_inotifymask (Lisp_Object symb
)
189 if (EQ (symb
, Qaccess
))
191 else if (EQ (symb
, Qattrib
))
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
))
199 else if (EQ (symb
, Qdelete
))
201 else if (EQ (symb
, Qdelete_self
))
202 return IN_DELETE_SELF
;
203 else if (EQ (symb
, Qmodify
))
205 else if (EQ (symb
, Qmove_self
))
207 else if (EQ (symb
, Qmoved_from
))
208 return IN_MOVED_FROM
;
209 else if (EQ (symb
, Qmoved_to
))
211 else if (EQ (symb
, Qopen
))
213 else if (EQ (symb
, Qmove
))
215 else if (EQ (symb
, Qclose
))
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
))
224 else if (EQ (symb
, Qoneshot
))
226 else if (EQ (symb
, Qonlydir
))
229 else if (EQ (symb
, Qt
) || EQ (symb
, Qall_events
))
230 return IN_ALL_EVENTS
;
234 report_inotify_error ("Unknown aspect", symb
);
239 aspect_to_inotifymask (Lisp_Object aspect
)
243 Lisp_Object x
= aspect
;
247 mask
|= symbol_to_inotifymask (XCAR (x
));
253 return symbol_to_inotifymask (aspect
);
256 DEFUN ("inotify-add-watch", Finotify_add_watch
, Sinotify_add_watch
, 3, 3, 0,
257 doc
: /* Add a watch for FILE-NAME to inotify.
259 Return a watch descriptor. The watch will look for ASPECT events and
260 invoke CALLBACK when an event occurs.
262 ASPECT might be one of the following symbols or a list of those symbols:
281 The following symbols can also be added to a list of aspects:
289 Watching a directory is not recursive. CALLBACK is passed a single argument
290 EVENT which contains an event structure of the format
292 (WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
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
304 If a directory is watched then NAME is the name of file that caused the event.
306 COOKIE is an object that can be compared using `equal' to identify two matching
307 renames (moved-from and moved-to).
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.
313 (Lisp_Object file_name
, Lisp_Object aspect
, Lisp_Object callback
)
316 Lisp_Object watch_object
;
317 Lisp_Object encoded_file_name
;
318 Lisp_Object watch_descriptor
;
321 CHECK_STRING (file_name
);
325 inotifyfd
= inotify_init1 (IN_NONBLOCK
|IN_CLOEXEC
);
327 report_inotify_error ("File watching (inotify) is not available", Qnil
);
329 add_read_fd (inotifyfd
, &inotify_callback
, NULL
);
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
);
336 report_inotify_error ("Could not add watch for file", file_name
);
338 watch_descriptor
= make_watch_descriptor (watchdesc
);
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
);
345 /* Store watch object in watch list. */
346 watch_object
= Fcons (watch_descriptor
, callback
);
347 watch_list
= Fcons (watch_object
, watch_list
);
349 return watch_descriptor
;
352 DEFUN ("inotify-rm-watch", Finotify_rm_watch
, Sinotify_rm_watch
, 1, 1, 0,
353 doc
: /* Remove an existing WATCH-DESCRIPTOR.
355 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
357 See inotify_rm_watch(2) for more information.
359 (Lisp_Object watch_descriptor
)
361 Lisp_Object watch_object
;
362 int wd
= XINT (watch_descriptor
);
364 if (inotify_rm_watch (inotifyfd
, wd
) == -1)
365 report_inotify_error ("Could not rm watch", watch_descriptor
);
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
);
372 /* Cleanup if no more files are watched. */
373 if (NILP (watch_list
))
375 emacs_close (inotifyfd
);
376 delete_read_fd (inotifyfd
);
383 DEFUN ("inotify-valid-p", Finotify_valid_p
, Sinotify_valid_p
, 1, 1, 0,
384 doc
: /* "Check a watch specified by its WATCH-DESCRIPTOR.
386 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
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
392 (Lisp_Object watch_descriptor
)
394 Lisp_Object watch_object
= Fassoc (watch_descriptor
, watch_list
);
395 return NILP (watch_object
) ? Qnil
: Qt
;
399 syms_of_inotify (void)
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 */
415 DEFSYM (Qall_events
, "all-events"); /* IN_ALL_EVENTS */
416 DEFSYM (Qmove
, "move"); /* IN_MOVE */
417 DEFSYM (Qclose
, "close"); /* IN_CLOSE */
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 */
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 */
430 defsubr (&Sinotify_add_watch
);
431 defsubr (&Sinotify_rm_watch
);
432 defsubr (&Sinotify_valid_p
);
434 staticpro (&watch_list
);
436 Fprovide (intern_c_string ("inotify"), Qnil
);
439 #endif /* HAVE_INOTIFY */