1 /* Inotify support for Emacs
3 Copyright (C) 2012-2017 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 (at
10 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.
49 Format: (watch-descriptor name callback)
51 static Lisp_Object watch_list
;
54 make_watch_descriptor (int wd
)
56 /* TODO replace this with a Misc Object! */
57 return make_number (wd
);
61 mask_to_aspects (uint32_t mask
) {
62 Lisp_Object aspects
= Qnil
;
64 aspects
= Fcons (Qaccess
, aspects
);
66 aspects
= Fcons (Qattrib
, aspects
);
67 if (mask
& IN_CLOSE_WRITE
)
68 aspects
= Fcons (Qclose_write
, aspects
);
69 if (mask
& IN_CLOSE_NOWRITE
)
70 aspects
= Fcons (Qclose_nowrite
, aspects
);
72 aspects
= Fcons (Qcreate
, aspects
);
74 aspects
= Fcons (Qdelete
, aspects
);
75 if (mask
& IN_DELETE_SELF
)
76 aspects
= Fcons (Qdelete_self
, aspects
);
78 aspects
= Fcons (Qmodify
, aspects
);
79 if (mask
& IN_MOVE_SELF
)
80 aspects
= Fcons (Qmove_self
, aspects
);
81 if (mask
& IN_MOVED_FROM
)
82 aspects
= Fcons (Qmoved_from
, aspects
);
83 if (mask
& IN_MOVED_TO
)
84 aspects
= Fcons (Qmoved_to
, aspects
);
86 aspects
= Fcons (Qopen
, aspects
);
87 if (mask
& IN_IGNORED
)
88 aspects
= Fcons (Qignored
, aspects
);
90 aspects
= Fcons (Qisdir
, aspects
);
91 if (mask
& IN_Q_OVERFLOW
)
92 aspects
= Fcons (Qq_overflow
, aspects
);
93 if (mask
& IN_UNMOUNT
)
94 aspects
= Fcons (Qunmount
, aspects
);
99 inotifyevent_to_event (Lisp_Object watch_object
, struct inotify_event
const *ev
)
101 Lisp_Object name
= Qnil
;
104 size_t const len
= strlen (ev
->name
);
105 name
= make_unibyte_string (ev
->name
, min (len
, ev
->len
));
106 name
= DECODE_FILE (name
);
109 name
= XCAR (XCDR (watch_object
));
111 return list2 (list4 (make_watch_descriptor (ev
->wd
),
112 mask_to_aspects (ev
->mask
),
114 make_number (ev
->cookie
)),
115 Fnth (make_number (2), 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. */
121 inotify_callback (int fd
, void *_
)
123 struct input_event event
;
124 Lisp_Object watch_object
;
131 if (ioctl (fd
, FIONREAD
, &to_read
) == -1)
132 report_file_notify_error ("Error while retrieving file system events",
134 buffer
= xmalloc (to_read
);
135 n
= read (fd
, buffer
, to_read
);
139 report_file_notify_error ("Error while reading file system events", Qnil
);
143 event
.kind
= FILE_NOTIFY_EVENT
;
146 while (i
< (size_t)n
)
148 struct inotify_event
*ev
= (struct inotify_event
*) &buffer
[i
];
150 watch_object
= Fassoc (make_watch_descriptor (ev
->wd
), watch_list
);
151 if (!NILP (watch_object
))
153 event
.arg
= inotifyevent_to_event (watch_object
, ev
);
155 /* If event was removed automatically: Drop it from watch list. */
156 if (ev
->mask
& IN_IGNORED
)
157 watch_list
= Fdelete (watch_object
, watch_list
);
159 if (!NILP (event
.arg
))
160 kbd_buffer_store_event (&event
);
163 i
+= sizeof (*ev
) + ev
->len
;
170 symbol_to_inotifymask (Lisp_Object symb
)
172 if (EQ (symb
, Qaccess
))
174 else if (EQ (symb
, Qattrib
))
176 else if (EQ (symb
, Qclose_write
))
177 return IN_CLOSE_WRITE
;
178 else if (EQ (symb
, Qclose_nowrite
))
179 return IN_CLOSE_NOWRITE
;
180 else if (EQ (symb
, Qcreate
))
182 else if (EQ (symb
, Qdelete
))
184 else if (EQ (symb
, Qdelete_self
))
185 return IN_DELETE_SELF
;
186 else if (EQ (symb
, Qmodify
))
188 else if (EQ (symb
, Qmove_self
))
190 else if (EQ (symb
, Qmoved_from
))
191 return IN_MOVED_FROM
;
192 else if (EQ (symb
, Qmoved_to
))
194 else if (EQ (symb
, Qopen
))
196 else if (EQ (symb
, Qmove
))
198 else if (EQ (symb
, Qclose
))
201 else if (EQ (symb
, Qdont_follow
))
202 return IN_DONT_FOLLOW
;
203 else if (EQ (symb
, Qexcl_unlink
))
204 return IN_EXCL_UNLINK
;
205 else if (EQ (symb
, Qmask_add
))
207 else if (EQ (symb
, Qoneshot
))
209 else if (EQ (symb
, Qonlydir
))
212 else if (EQ (symb
, Qt
) || EQ (symb
, Qall_events
))
213 return IN_ALL_EVENTS
;
217 report_file_notify_error ("Unknown aspect", symb
);
222 aspect_to_inotifymask (Lisp_Object aspect
)
226 Lisp_Object x
= aspect
;
230 mask
|= symbol_to_inotifymask (XCAR (x
));
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:
264 The following symbols can also be added to a list of aspects:
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
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
)
299 Lisp_Object watch_object
;
300 Lisp_Object encoded_file_name
;
301 Lisp_Object watch_descriptor
;
304 CHECK_STRING (file_name
);
308 inotifyfd
= inotify_init1 (IN_NONBLOCK
|IN_CLOEXEC
);
310 report_file_notify_error ("File watching is not available", Qnil
);
312 add_read_fd (inotifyfd
, &inotify_callback
, NULL
);
315 mask
= aspect_to_inotifymask (aspect
);
316 encoded_file_name
= ENCODE_FILE (file_name
);
317 watchdesc
= inotify_add_watch (inotifyfd
, SSDATA (encoded_file_name
), mask
);
319 report_file_notify_error ("Could not add watch for file", file_name
);
321 watch_descriptor
= make_watch_descriptor (watchdesc
);
323 /* Delete existing watch object. */
324 watch_object
= Fassoc (watch_descriptor
, watch_list
);
325 if (!NILP (watch_object
))
326 watch_list
= Fdelete (watch_object
, watch_list
);
328 /* Store watch object in watch list. */
329 watch_object
= list3 (watch_descriptor
, encoded_file_name
, callback
);
330 watch_list
= Fcons (watch_object
, watch_list
);
332 return watch_descriptor
;
335 DEFUN ("inotify-rm-watch", Finotify_rm_watch
, Sinotify_rm_watch
, 1, 1, 0,
336 doc
: /* Remove an existing WATCH-DESCRIPTOR.
338 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
340 See inotify_rm_watch(2) for more information.
342 (Lisp_Object watch_descriptor
)
344 Lisp_Object watch_object
;
345 int wd
= XINT (watch_descriptor
);
347 if (inotify_rm_watch (inotifyfd
, wd
) == -1)
348 report_file_notify_error ("Could not rm watch", watch_descriptor
);
350 /* Remove watch descriptor from watch list. */
351 watch_object
= Fassoc (watch_descriptor
, watch_list
);
352 if (!NILP (watch_object
))
353 watch_list
= Fdelete (watch_object
, watch_list
);
355 /* Cleanup if no more files are watched. */
356 if (NILP (watch_list
))
358 emacs_close (inotifyfd
);
359 delete_read_fd (inotifyfd
);
366 DEFUN ("inotify-valid-p", Finotify_valid_p
, Sinotify_valid_p
, 1, 1, 0,
367 doc
: /* Check a watch specified by its WATCH-DESCRIPTOR.
369 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
371 A watch can become invalid if the file or directory it watches is
372 deleted, or if the watcher thread exits abnormally for any other
373 reason. Removing the watch by calling `inotify-rm-watch' also makes
375 (Lisp_Object watch_descriptor
)
377 Lisp_Object watch_object
= Fassoc (watch_descriptor
, watch_list
);
378 return NILP (watch_object
) ? Qnil
: Qt
;
382 syms_of_inotify (void)
384 DEFSYM (Qaccess
, "access"); /* IN_ACCESS */
385 DEFSYM (Qattrib
, "attrib"); /* IN_ATTRIB */
386 DEFSYM (Qclose_write
, "close-write"); /* IN_CLOSE_WRITE */
387 DEFSYM (Qclose_nowrite
, "close-nowrite");
388 /* IN_CLOSE_NOWRITE */
389 DEFSYM (Qcreate
, "create"); /* IN_CREATE */
390 DEFSYM (Qdelete
, "delete"); /* IN_DELETE */
391 DEFSYM (Qdelete_self
, "delete-self"); /* IN_DELETE_SELF */
392 DEFSYM (Qmodify
, "modify"); /* IN_MODIFY */
393 DEFSYM (Qmove_self
, "move-self"); /* IN_MOVE_SELF */
394 DEFSYM (Qmoved_from
, "moved-from"); /* IN_MOVED_FROM */
395 DEFSYM (Qmoved_to
, "moved-to"); /* IN_MOVED_TO */
396 DEFSYM (Qopen
, "open"); /* IN_OPEN */
398 DEFSYM (Qall_events
, "all-events"); /* IN_ALL_EVENTS */
399 DEFSYM (Qmove
, "move"); /* IN_MOVE */
400 DEFSYM (Qclose
, "close"); /* IN_CLOSE */
402 DEFSYM (Qdont_follow
, "dont-follow"); /* IN_DONT_FOLLOW */
403 DEFSYM (Qexcl_unlink
, "excl-unlink"); /* IN_EXCL_UNLINK */
404 DEFSYM (Qmask_add
, "mask-add"); /* IN_MASK_ADD */
405 DEFSYM (Qoneshot
, "oneshot"); /* IN_ONESHOT */
406 DEFSYM (Qonlydir
, "onlydir"); /* IN_ONLYDIR */
408 DEFSYM (Qignored
, "ignored"); /* IN_IGNORED */
409 DEFSYM (Qisdir
, "isdir"); /* IN_ISDIR */
410 DEFSYM (Qq_overflow
, "q-overflow"); /* IN_Q_OVERFLOW */
411 DEFSYM (Qunmount
, "unmount"); /* IN_UNMOUNT */
413 defsubr (&Sinotify_add_watch
);
414 defsubr (&Sinotify_rm_watch
);
415 defsubr (&Sinotify_valid_p
);
417 staticpro (&watch_list
);
419 Fprovide (intern_c_string ("inotify"), Qnil
);
422 #endif /* HAVE_INOTIFY */