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 /* This callback is called when the FD is available for read. The inotify
120 events are read from FD and converted into input_events. */
122 inotify_callback (int fd
, void *_
)
124 struct input_event event
;
125 Lisp_Object watch_object
;
132 if (ioctl (fd
, FIONREAD
, &to_read
) == -1)
133 report_file_notify_error ("Error while retrieving file system events",
135 buffer
= xmalloc (to_read
);
136 n
= read (fd
, buffer
, to_read
);
140 report_file_notify_error ("Error while reading file system events", Qnil
);
144 event
.kind
= FILE_NOTIFY_EVENT
;
147 while (i
< (size_t)n
)
149 struct inotify_event
*ev
= (struct inotify_event
*)&buffer
[i
];
151 watch_object
= Fassoc (make_watch_descriptor (ev
->wd
), watch_list
);
152 if (!NILP (watch_object
))
154 event
.arg
= inotifyevent_to_event (watch_object
, ev
);
156 /* If event was removed automatically: Drop it from watch list. */
157 if (ev
->mask
& IN_IGNORED
)
158 watch_list
= Fdelete (watch_object
, watch_list
);
160 if (!NILP (event
.arg
))
161 kbd_buffer_store_event (&event
);
164 i
+= sizeof (*ev
) + ev
->len
;
171 symbol_to_inotifymask (Lisp_Object symb
)
173 if (EQ (symb
, Qaccess
))
175 else if (EQ (symb
, Qattrib
))
177 else if (EQ (symb
, Qclose_write
))
178 return IN_CLOSE_WRITE
;
179 else if (EQ (symb
, Qclose_nowrite
))
180 return IN_CLOSE_NOWRITE
;
181 else if (EQ (symb
, Qcreate
))
183 else if (EQ (symb
, Qdelete
))
185 else if (EQ (symb
, Qdelete_self
))
186 return IN_DELETE_SELF
;
187 else if (EQ (symb
, Qmodify
))
189 else if (EQ (symb
, Qmove_self
))
191 else if (EQ (symb
, Qmoved_from
))
192 return IN_MOVED_FROM
;
193 else if (EQ (symb
, Qmoved_to
))
195 else if (EQ (symb
, Qopen
))
197 else if (EQ (symb
, Qmove
))
199 else if (EQ (symb
, Qclose
))
202 else if (EQ (symb
, Qdont_follow
))
203 return IN_DONT_FOLLOW
;
204 else if (EQ (symb
, Qexcl_unlink
))
205 return IN_EXCL_UNLINK
;
206 else if (EQ (symb
, Qmask_add
))
208 else if (EQ (symb
, Qoneshot
))
210 else if (EQ (symb
, Qonlydir
))
213 else if (EQ (symb
, Qt
) || EQ (symb
, Qall_events
))
214 return IN_ALL_EVENTS
;
218 report_file_notify_error ("Unknown aspect", symb
);
223 aspect_to_inotifymask (Lisp_Object aspect
)
227 Lisp_Object x
= aspect
;
231 mask
|= symbol_to_inotifymask (XCAR (x
));
237 return symbol_to_inotifymask (aspect
);
240 DEFUN ("inotify-add-watch", Finotify_add_watch
, Sinotify_add_watch
, 3, 3, 0,
241 doc
: /* Add a watch for FILE-NAME to inotify.
243 Return a watch descriptor. The watch will look for ASPECT events and
244 invoke CALLBACK when an event occurs.
246 ASPECT might be one of the following symbols or a list of those symbols:
265 The following symbols can also be added to a list of aspects:
273 Watching a directory is not recursive. CALLBACK is passed a single argument
274 EVENT which contains an event structure of the format
276 (WATCH-DESCRIPTOR ASPECTS NAME COOKIE)
278 WATCH-DESCRIPTOR is the same object that was returned by this function. It can
279 be tested for equality using `equal'. ASPECTS describes the event. It is a
280 list of ASPECT symbols described above and can also contain one of the following
288 If a directory is watched then NAME is the name of file that caused the event.
290 COOKIE is an object that can be compared using `equal' to identify two matching
291 renames (moved-from and moved-to).
293 See inotify(7) and inotify_add_watch(2) for further information. The inotify fd
294 is managed internally and there is no corresponding inotify_init. Use
295 `inotify-rm-watch' to remove a watch.
297 (Lisp_Object file_name
, Lisp_Object aspect
, Lisp_Object callback
)
300 Lisp_Object watch_object
;
301 Lisp_Object encoded_file_name
;
302 Lisp_Object watch_descriptor
;
305 CHECK_STRING (file_name
);
309 inotifyfd
= inotify_init1 (IN_NONBLOCK
|IN_CLOEXEC
);
311 report_file_notify_error ("File watching is not available", Qnil
);
313 add_read_fd (inotifyfd
, &inotify_callback
, NULL
);
316 mask
= aspect_to_inotifymask (aspect
);
317 encoded_file_name
= ENCODE_FILE (file_name
);
318 watchdesc
= inotify_add_watch (inotifyfd
, SSDATA (encoded_file_name
), mask
);
320 report_file_notify_error ("Could not add watch for file", file_name
);
322 watch_descriptor
= make_watch_descriptor (watchdesc
);
324 /* Delete existing watch object. */
325 watch_object
= Fassoc (watch_descriptor
, watch_list
);
326 if (!NILP (watch_object
))
327 watch_list
= Fdelete (watch_object
, watch_list
);
329 /* Store watch object in watch list. */
330 watch_object
= Fcons (watch_descriptor
, callback
);
331 watch_list
= Fcons (watch_object
, watch_list
);
333 return watch_descriptor
;
336 DEFUN ("inotify-rm-watch", Finotify_rm_watch
, Sinotify_rm_watch
, 1, 1, 0,
337 doc
: /* Remove an existing WATCH-DESCRIPTOR.
339 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
341 See inotify_rm_watch(2) for more information.
343 (Lisp_Object watch_descriptor
)
345 Lisp_Object watch_object
;
346 int wd
= XINT (watch_descriptor
);
348 if (inotify_rm_watch (inotifyfd
, wd
) == -1)
349 report_file_notify_error ("Could not rm watch", watch_descriptor
);
351 /* Remove watch descriptor from watch list. */
352 watch_object
= Fassoc (watch_descriptor
, watch_list
);
353 if (!NILP (watch_object
))
354 watch_list
= Fdelete (watch_object
, watch_list
);
356 /* Cleanup if no more files are watched. */
357 if (NILP (watch_list
))
359 emacs_close (inotifyfd
);
360 delete_read_fd (inotifyfd
);
367 DEFUN ("inotify-valid-p", Finotify_valid_p
, Sinotify_valid_p
, 1, 1, 0,
368 doc
: /* "Check a watch specified by its WATCH-DESCRIPTOR.
370 WATCH-DESCRIPTOR should be an object returned by `inotify-add-watch'.
372 A watch can become invalid if the file or directory it watches is
373 deleted, or if the watcher thread exits abnormally for any other
374 reason. Removing the watch by calling `inotify-rm-watch' also makes
376 (Lisp_Object watch_descriptor
)
378 Lisp_Object watch_object
= Fassoc (watch_descriptor
, watch_list
);
379 return NILP (watch_object
) ? Qnil
: Qt
;
383 syms_of_inotify (void)
385 DEFSYM (Qaccess
, "access"); /* IN_ACCESS */
386 DEFSYM (Qattrib
, "attrib"); /* IN_ATTRIB */
387 DEFSYM (Qclose_write
, "close-write"); /* IN_CLOSE_WRITE */
388 DEFSYM (Qclose_nowrite
, "close-nowrite");
389 /* IN_CLOSE_NOWRITE */
390 DEFSYM (Qcreate
, "create"); /* IN_CREATE */
391 DEFSYM (Qdelete
, "delete"); /* IN_DELETE */
392 DEFSYM (Qdelete_self
, "delete-self"); /* IN_DELETE_SELF */
393 DEFSYM (Qmodify
, "modify"); /* IN_MODIFY */
394 DEFSYM (Qmove_self
, "move-self"); /* IN_MOVE_SELF */
395 DEFSYM (Qmoved_from
, "moved-from"); /* IN_MOVED_FROM */
396 DEFSYM (Qmoved_to
, "moved-to"); /* IN_MOVED_TO */
397 DEFSYM (Qopen
, "open"); /* IN_OPEN */
399 DEFSYM (Qall_events
, "all-events"); /* IN_ALL_EVENTS */
400 DEFSYM (Qmove
, "move"); /* IN_MOVE */
401 DEFSYM (Qclose
, "close"); /* IN_CLOSE */
403 DEFSYM (Qdont_follow
, "dont-follow"); /* IN_DONT_FOLLOW */
404 DEFSYM (Qexcl_unlink
, "excl-unlink"); /* IN_EXCL_UNLINK */
405 DEFSYM (Qmask_add
, "mask-add"); /* IN_MASK_ADD */
406 DEFSYM (Qoneshot
, "oneshot"); /* IN_ONESHOT */
407 DEFSYM (Qonlydir
, "onlydir"); /* IN_ONLYDIR */
409 DEFSYM (Qignored
, "ignored"); /* IN_IGNORED */
410 DEFSYM (Qisdir
, "isdir"); /* IN_ISDIR */
411 DEFSYM (Qq_overflow
, "q-overflow"); /* IN_Q_OVERFLOW */
412 DEFSYM (Qunmount
, "unmount"); /* IN_UNMOUNT */
414 defsubr (&Sinotify_add_watch
);
415 defsubr (&Sinotify_rm_watch
);
416 defsubr (&Sinotify_valid_p
);
418 staticpro (&watch_list
);
420 Fprovide (intern_c_string ("inotify"), Qnil
);
423 #endif /* HAVE_INOTIFY */