1 /* Inotify support for Emacs
3 Copyright (C) 2012-2016 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 "termhooks.h"
31 #include <sys/inotify.h>
32 #include <sys/ioctl.h>
34 /* Ignore bits that might be undefined on old GNU/Linux systems. */
35 #ifndef IN_EXCL_UNLINK
36 # define IN_EXCL_UNLINK 0
38 #ifndef IN_DONT_FOLLOW
39 # define IN_DONT_FOLLOW 0
45 /* File handle for inotify. */
46 static int inotifyfd
= -1;
48 /* Assoc list of files being watched.
50 (watch-descriptor . callback)
52 static Lisp_Object watch_list
;
55 make_watch_descriptor (int wd
)
57 /* TODO replace this with a Misc Object! */
58 return make_number (wd
);
62 mask_to_aspects (uint32_t mask
) {
63 Lisp_Object aspects
= Qnil
;
65 aspects
= Fcons (Qaccess
, aspects
);
67 aspects
= Fcons (Qattrib
, aspects
);
68 if (mask
& IN_CLOSE_WRITE
)
69 aspects
= Fcons (Qclose_write
, aspects
);
70 if (mask
& IN_CLOSE_NOWRITE
)
71 aspects
= Fcons (Qclose_nowrite
, aspects
);
73 aspects
= Fcons (Qcreate
, aspects
);
75 aspects
= Fcons (Qdelete
, aspects
);
76 if (mask
& IN_DELETE_SELF
)
77 aspects
= Fcons (Qdelete_self
, aspects
);
79 aspects
= Fcons (Qmodify
, aspects
);
80 if (mask
& IN_MOVE_SELF
)
81 aspects
= Fcons (Qmove_self
, aspects
);
82 if (mask
& IN_MOVED_FROM
)
83 aspects
= Fcons (Qmoved_from
, aspects
);
84 if (mask
& IN_MOVED_TO
)
85 aspects
= Fcons (Qmoved_to
, aspects
);
87 aspects
= Fcons (Qopen
, aspects
);
88 if (mask
& IN_IGNORED
)
89 aspects
= Fcons (Qignored
, aspects
);
91 aspects
= Fcons (Qisdir
, aspects
);
92 if (mask
& IN_Q_OVERFLOW
)
93 aspects
= Fcons (Qq_overflow
, aspects
);
94 if (mask
& IN_UNMOUNT
)
95 aspects
= Fcons (Qunmount
, aspects
);
100 inotifyevent_to_event (Lisp_Object watch_object
, struct inotify_event
const *ev
)
102 Lisp_Object name
= Qnil
;
105 size_t const len
= strlen (ev
->name
);
106 name
= make_unibyte_string (ev
->name
, min (len
, ev
->len
));
107 name
= DECODE_FILE (name
);
110 return list2 (list4 (make_watch_descriptor (ev
->wd
),
111 mask_to_aspects (ev
->mask
),
113 make_number (ev
->cookie
)),
114 XCDR (watch_object
));
117 /* This callback is called when the FD is available for read. The inotify
118 events are read from FD and converted into input_events. */
120 inotify_callback (int fd
, void *_
)
122 struct input_event event
;
123 Lisp_Object watch_object
;
130 if (ioctl (fd
, FIONREAD
, &to_read
) == -1)
131 report_file_notify_error ("Error while retrieving file system events",
133 buffer
= xmalloc (to_read
);
134 n
= read (fd
, buffer
, to_read
);
138 report_file_notify_error ("Error while reading file system events", Qnil
);
142 event
.kind
= FILE_NOTIFY_EVENT
;
145 while (i
< (size_t)n
)
147 struct inotify_event
*ev
= (struct inotify_event
*)&buffer
[i
];
149 watch_object
= Fassoc (make_watch_descriptor (ev
->wd
), watch_list
);
150 if (!NILP (watch_object
))
152 event
.arg
= inotifyevent_to_event (watch_object
, ev
);
154 /* If event was removed automatically: Drop it from watch list. */
155 if (ev
->mask
& IN_IGNORED
)
156 watch_list
= Fdelete (watch_object
, watch_list
);
158 if (!NILP (event
.arg
))
159 kbd_buffer_store_event (&event
);
162 i
+= sizeof (*ev
) + ev
->len
;
169 symbol_to_inotifymask (Lisp_Object symb
)
171 if (EQ (symb
, Qaccess
))
173 else if (EQ (symb
, Qattrib
))
175 else if (EQ (symb
, Qclose_write
))
176 return IN_CLOSE_WRITE
;
177 else if (EQ (symb
, Qclose_nowrite
))
178 return IN_CLOSE_NOWRITE
;
179 else if (EQ (symb
, Qcreate
))
181 else if (EQ (symb
, Qdelete
))
183 else if (EQ (symb
, Qdelete_self
))
184 return IN_DELETE_SELF
;
185 else if (EQ (symb
, Qmodify
))
187 else if (EQ (symb
, Qmove_self
))
189 else if (EQ (symb
, Qmoved_from
))
190 return IN_MOVED_FROM
;
191 else if (EQ (symb
, Qmoved_to
))
193 else if (EQ (symb
, Qopen
))
195 else if (EQ (symb
, Qmove
))
197 else if (EQ (symb
, Qclose
))
200 else if (EQ (symb
, Qdont_follow
))
201 return IN_DONT_FOLLOW
;
202 else if (EQ (symb
, Qexcl_unlink
))
203 return IN_EXCL_UNLINK
;
204 else if (EQ (symb
, Qmask_add
))
206 else if (EQ (symb
, Qoneshot
))
208 else if (EQ (symb
, Qonlydir
))
211 else if (EQ (symb
, Qt
) || EQ (symb
, Qall_events
))
212 return IN_ALL_EVENTS
;
216 report_file_notify_error ("Unknown aspect", symb
);
221 aspect_to_inotifymask (Lisp_Object aspect
)
225 Lisp_Object x
= aspect
;
229 mask
|= symbol_to_inotifymask (XCAR (x
));
235 return symbol_to_inotifymask (aspect
);
238 DEFUN ("inotify-add-watch", Finotify_add_watch
, Sinotify_add_watch
, 3, 3, 0,
239 doc
: /* Add a watch for FILE-NAME to inotify.
241 Return a watch descriptor. The watch will look for ASPECT events and
242 invoke CALLBACK when an event occurs.
244 ASPECT might be one of the following symbols or a list of those symbols:
263 The following symbols can also be added to a list of aspects:
271 Watching a directory is not recursive. CALLBACK is passed a single argument
272 EVENT which contains an event structure of the format
274 (WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
276 WATCH-DESCRIPTOR is the same object that was returned by this function. It can
277 be tested for equality using `equal'. ASPECTS describes the event. It is a
278 list of ASPECT symbols described above and can also contain one of the following
286 If a directory is watched then NAME is the name of file that caused the event.
288 COOKIE is an object that can be compared using `equal' to identify two matching
289 renames (moved-from and moved-to).
291 See inotify(7) and inotify_add_watch(2) for further information. The inotify fd
292 is managed internally and there is no corresponding inotify_init. Use
293 `inotify-rm-watch' to remove a watch.
295 (Lisp_Object file_name
, Lisp_Object aspect
, Lisp_Object callback
)
298 Lisp_Object watch_object
;
299 Lisp_Object encoded_file_name
;
300 Lisp_Object watch_descriptor
;
303 CHECK_STRING (file_name
);
307 inotifyfd
= inotify_init1 (IN_NONBLOCK
|IN_CLOEXEC
);
309 report_file_notify_error ("File watching is not available", Qnil
);
311 add_read_fd (inotifyfd
, &inotify_callback
, NULL
);
314 mask
= aspect_to_inotifymask (aspect
);
315 encoded_file_name
= ENCODE_FILE (file_name
);
316 watchdesc
= inotify_add_watch (inotifyfd
, SSDATA (encoded_file_name
), mask
);
318 report_file_notify_error ("Could not add watch for file", file_name
);
320 watch_descriptor
= make_watch_descriptor (watchdesc
);
322 /* Delete existing watch object. */
323 watch_object
= Fassoc (watch_descriptor
, watch_list
);
324 if (!NILP (watch_object
))
325 watch_list
= Fdelete (watch_object
, watch_list
);
327 /* Store watch object in watch list. */
328 watch_object
= Fcons (watch_descriptor
, callback
);
329 watch_list
= Fcons (watch_object
, watch_list
);
331 return watch_descriptor
;
334 DEFUN ("inotify-rm-watch", Finotify_rm_watch
, Sinotify_rm_watch
, 1, 1, 0,
335 doc
: /* Remove an existing WATCH-DESCRIPTOR.
337 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
339 See inotify_rm_watch(2) for more information.
341 (Lisp_Object watch_descriptor
)
343 Lisp_Object watch_object
;
344 int wd
= XINT (watch_descriptor
);
346 if (inotify_rm_watch (inotifyfd
, wd
) == -1)
347 report_file_notify_error ("Could not rm watch", watch_descriptor
);
349 /* Remove watch descriptor from watch list. */
350 watch_object
= Fassoc (watch_descriptor
, watch_list
);
351 if (!NILP (watch_object
))
352 watch_list
= Fdelete (watch_object
, watch_list
);
354 /* Cleanup if no more files are watched. */
355 if (NILP (watch_list
))
357 emacs_close (inotifyfd
);
358 delete_read_fd (inotifyfd
);
365 DEFUN ("inotify-valid-p", Finotify_valid_p
, Sinotify_valid_p
, 1, 1, 0,
366 doc
: /* "Check a watch specified by its WATCH-DESCRIPTOR.
368 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
370 A watch can become invalid if the file or directory it watches is
371 deleted, or if the watcher thread exits abnormally for any other
372 reason. Removing the watch by calling `inotify-rm-watch' also makes
374 (Lisp_Object watch_descriptor
)
376 Lisp_Object watch_object
= Fassoc (watch_descriptor
, watch_list
);
377 return NILP (watch_object
) ? Qnil
: Qt
;
381 syms_of_inotify (void)
383 DEFSYM (Qaccess
, "access"); /* IN_ACCESS */
384 DEFSYM (Qattrib
, "attrib"); /* IN_ATTRIB */
385 DEFSYM (Qclose_write
, "close-write"); /* IN_CLOSE_WRITE */
386 DEFSYM (Qclose_nowrite
, "close-nowrite");
387 /* IN_CLOSE_NOWRITE */
388 DEFSYM (Qcreate
, "create"); /* IN_CREATE */
389 DEFSYM (Qdelete
, "delete"); /* IN_DELETE */
390 DEFSYM (Qdelete_self
, "delete-self"); /* IN_DELETE_SELF */
391 DEFSYM (Qmodify
, "modify"); /* IN_MODIFY */
392 DEFSYM (Qmove_self
, "move-self"); /* IN_MOVE_SELF */
393 DEFSYM (Qmoved_from
, "moved-from"); /* IN_MOVED_FROM */
394 DEFSYM (Qmoved_to
, "moved-to"); /* IN_MOVED_TO */
395 DEFSYM (Qopen
, "open"); /* IN_OPEN */
397 DEFSYM (Qall_events
, "all-events"); /* IN_ALL_EVENTS */
398 DEFSYM (Qmove
, "move"); /* IN_MOVE */
399 DEFSYM (Qclose
, "close"); /* IN_CLOSE */
401 DEFSYM (Qdont_follow
, "dont-follow"); /* IN_DONT_FOLLOW */
402 DEFSYM (Qexcl_unlink
, "excl-unlink"); /* IN_EXCL_UNLINK */
403 DEFSYM (Qmask_add
, "mask-add"); /* IN_MASK_ADD */
404 DEFSYM (Qoneshot
, "oneshot"); /* IN_ONESHOT */
405 DEFSYM (Qonlydir
, "onlydir"); /* IN_ONLYDIR */
407 DEFSYM (Qignored
, "ignored"); /* IN_IGNORED */
408 DEFSYM (Qisdir
, "isdir"); /* IN_ISDIR */
409 DEFSYM (Qq_overflow
, "q-overflow"); /* IN_Q_OVERFLOW */
410 DEFSYM (Qunmount
, "unmount"); /* IN_UNMOUNT */
412 defsubr (&Sinotify_add_watch
);
413 defsubr (&Sinotify_rm_watch
);
414 defsubr (&Sinotify_valid_p
);
416 staticpro (&watch_list
);
418 Fprovide (intern_c_string ("inotify"), Qnil
);
421 #endif /* HAVE_INOTIFY */