1 /* Filesystem notifications support with kqueue API.
2 Copyright (C) 2015 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
23 #include <sys/types.h>
24 #include <sys/event.h>
32 /* File handle for kqueue. */
33 static int kqueuefd
= -1;
35 /* This is a list, elements are (DESCRIPTOR FILE FLAGS CALLBACK [DIRLIST]). */
36 static Lisp_Object watch_list
;
38 /* Generate a list from the directory_files_internal output.
39 Items are (INODE FILE-NAME LAST-MOD LAST-STATUS-MOD SIZE). */
41 kqueue_directory_listing (Lisp_Object directory_files
)
43 Lisp_Object dl
, result
= Qnil
;
45 for (dl
= directory_files
; ! NILP (dl
); dl
= XCDR (dl
)) {
46 /* We ignore "." and "..". */
47 if ((strcmp (".", SSDATA (XCAR (XCAR (dl
)))) == 0) ||
48 (strcmp ("..", SSDATA (XCAR (XCAR (dl
)))) == 0))
53 Fnth (make_number (11), XCAR (dl
)),
56 /* last modification time. */
57 Fnth (make_number (6), XCAR (dl
)),
58 /* last status change time. */
59 Fnth (make_number (7), XCAR (dl
)),
61 Fnth (make_number (8), XCAR (dl
))),
67 /* Generate a file notification event. */
70 (Lisp_Object watch_object
, Lisp_Object actions
,
71 Lisp_Object file
, Lisp_Object file1
)
73 Lisp_Object flags
, action
, entry
;
74 struct input_event event
;
76 /* Check, whether all actions shall be monitored. */
77 flags
= Fnth (make_number (2), watch_object
);
82 entry
= XCAR (action
);
83 if (NILP (Fmember (entry
, flags
))) {
84 action
= XCDR (action
);
85 actions
= Fdelq (entry
, actions
);
87 action
= XCDR (action
);
90 /* Store it into the input event queue. */
91 if (! NILP (actions
)) {
93 event
.kind
= FILE_NOTIFY_EVENT
;
94 event
.frame_or_window
= Qnil
;
95 event
.arg
= list2 (Fcons (XCAR (watch_object
),
99 : list2 (file
, file1
))),
100 Fnth (make_number (3), watch_object
));
101 kbd_buffer_store_event (&event
);
105 /* This compares two directory listings in case of a `write' event for
106 a directory. Generate resulting file notification events. The old
107 directory listing is retrieved from watch_object, it will be
108 replaced by the new directory listing at the end of this
111 kqueue_compare_dir_list
112 (Lisp_Object watch_object
)
114 Lisp_Object dir
, pending_events
;
115 Lisp_Object old_directory_files
, old_dl
, new_directory_files
, new_dl
, dl
;
117 dir
= XCAR (XCDR (watch_object
));
118 pending_events
= Qnil
;
120 old_directory_files
= Fnth (make_number (4), watch_object
);
121 old_dl
= kqueue_directory_listing (old_directory_files
);
123 /* When the directory is not accessible anymore, it has been deleted. */
124 if (NILP (Ffile_directory_p (dir
))) {
125 kqueue_generate_event (watch_object
, Fcons (Qdelete
, Qnil
), dir
, Qnil
);
128 new_directory_files
=
129 directory_files_internal (dir
, Qnil
, Qnil
, Qnil
, 1, Qnil
);
130 new_dl
= kqueue_directory_listing (new_directory_files
);
132 /* Parse through the old list. */
135 Lisp_Object old_entry
, new_entry
, dl1
;
139 /* Search for an entry with the same inode. */
140 old_entry
= XCAR (dl
);
141 new_entry
= assq_no_quit (XCAR (old_entry
), new_dl
);
142 if (! NILP (Fequal (old_entry
, new_entry
))) {
143 /* Both entries are identical. Nothing to do. */
144 new_dl
= Fdelq (new_entry
, new_dl
);
148 /* Both entries have the same inode. */
149 if (! NILP (new_entry
)) {
150 /* Both entries have the same file name. */
151 if (strcmp (SSDATA (XCAR (XCDR (old_entry
))),
152 SSDATA (XCAR (XCDR (new_entry
)))) == 0) {
153 /* Modification time has been changed, the file has been written. */
154 if (NILP (Fequal (Fnth (make_number (2), old_entry
),
155 Fnth (make_number (2), new_entry
))))
156 kqueue_generate_event
157 (watch_object
, Fcons (Qwrite
, Qnil
), XCAR (XCDR (old_entry
)), Qnil
);
158 /* Status change time has been changed, the file attributes
160 if (NILP (Fequal (Fnth (make_number (3), old_entry
),
161 Fnth (make_number (3), new_entry
))))
162 kqueue_generate_event
163 (watch_object
, Fcons (Qattrib
, Qnil
),
164 XCAR (XCDR (old_entry
)), Qnil
);
167 /* The file has been renamed. */
168 kqueue_generate_event
169 (watch_object
, Fcons (Qrename
, Qnil
),
170 XCAR (XCDR (old_entry
)), XCAR (XCDR (new_entry
)));
172 new_dl
= Fdelq (new_entry
, new_dl
);
176 /* Search, whether there is a file with the same name but another
178 for (dl1
= new_dl
; ! NILP (dl1
); dl1
= XCDR (dl1
)) {
179 new_entry
= XCAR (dl1
);
180 if (strcmp (SSDATA (XCAR (XCDR (old_entry
))),
181 SSDATA (XCAR (XCDR (new_entry
)))) == 0) {
182 pending_events
= Fcons (new_entry
, pending_events
);
183 new_dl
= Fdelq (new_entry
, new_dl
);
188 new_entry
= assq_no_quit (XCAR (old_entry
), pending_events
);
189 if (NILP (new_entry
))
190 /* The file has been deleted. */
191 kqueue_generate_event
192 (watch_object
, Fcons (Qdelete
, Qnil
), XCAR (XCDR (old_entry
)), Qnil
);
194 /* The file has been renamed. */
195 kqueue_generate_event
196 (watch_object
, Fcons (Qrename
, Qnil
),
197 XCAR (XCDR (old_entry
)), XCAR (XCDR (new_entry
)));
198 new_dl
= Fdelq (new_entry
, new_dl
);
199 pending_events
= Fdelq (new_entry
, pending_events
);
204 old_dl
= Fdelq (old_entry
, old_dl
);
207 /* Parse through the resulting new list. */
214 /* A new file has appeared. */
216 kqueue_generate_event
217 (watch_object
, Fcons (Qcreate
, Qnil
), XCAR (XCDR (entry
)), Qnil
);
219 /* Check size of that file. */
220 Lisp_Object size
= Fnth (make_number (4), entry
);
221 if (FLOATP (size
) || (XINT (size
) > 0))
222 kqueue_generate_event
223 (watch_object
, Fcons (Qwrite
, Qnil
), XCAR (XCDR (entry
)), Qnil
);
226 new_dl
= Fdelq (entry
, new_dl
);
229 /* Parse through the resulting pending_events_list. */
236 /* A file is still pending. Assume it was a write. */
238 kqueue_generate_event
239 (watch_object
, Fcons (Qwrite
, Qnil
), XCAR (XCDR (entry
)), Qnil
);
242 pending_events
= Fdelq (entry
, pending_events
);
245 /* At this point, old_dl, new_dl and pending_events shall be empty.
246 Let's make a check for this (might be removed once the code is
249 report_file_error ("Old list not empty", old_dl
);
251 report_file_error ("New list not empty", new_dl
);
252 if (! NILP (pending_events
))
253 report_file_error ("Pending events not empty", new_dl
);
255 /* Replace old directory listing with the new one. */
256 XSETCDR (Fnthcdr (make_number (3), watch_object
),
257 Fcons (new_directory_files
, Qnil
));
261 /* This is the callback function for arriving input on kqueuefd. It
262 shall create a Lisp event, and put it into the Emacs input queue. */
264 kqueue_callback (int fd
, void *data
)
268 static const struct timespec nullts
= { 0, 0 };
269 Lisp_Object descriptor
, watch_object
, file
, actions
;
271 /* Read one event. */
272 int ret
= kevent (kqueuefd
, NULL
, 0, &kev
, 1, &nullts
);
274 /* All events read. */
278 /* Determine descriptor and file name. */
279 descriptor
= make_number (kev
.ident
);
280 watch_object
= assq_no_quit (descriptor
, watch_list
);
281 if (CONSP (watch_object
))
282 file
= XCAR (XCDR (watch_object
));
286 /* Determine event actions. */
288 if (kev
.fflags
& NOTE_DELETE
)
289 actions
= Fcons (Qdelete
, actions
);
290 if (kev
.fflags
& NOTE_WRITE
) {
291 /* Check, whether this is a directory event. */
292 if (NILP (Fnth (make_number (4), watch_object
)))
293 actions
= Fcons (Qwrite
, actions
);
295 kqueue_compare_dir_list (watch_object
);
297 if (kev
.fflags
& NOTE_EXTEND
)
298 actions
= Fcons (Qextend
, actions
);
299 if (kev
.fflags
& NOTE_ATTRIB
)
300 actions
= Fcons (Qattrib
, actions
);
301 if (kev
.fflags
& NOTE_LINK
)
302 actions
= Fcons (Qlink
, actions
);
303 /* It would be useful to know the target of the rename operation.
304 At this point, it is not possible. Happens only when the upper
305 directory is monitored. */
306 if (kev
.fflags
& NOTE_RENAME
)
307 actions
= Fcons (Qrename
, actions
);
309 /* Create the event. */
310 if (! NILP (actions
))
311 kqueue_generate_event (watch_object
, actions
, file
, Qnil
);
313 /* Cancel monitor if file or directory is deleted or renamed. */
314 if (kev
.fflags
& (NOTE_DELETE
| NOTE_RENAME
))
315 Fkqueue_rm_watch (descriptor
);
320 DEFUN ("kqueue-add-watch", Fkqueue_add_watch
, Skqueue_add_watch
, 3, 3, 0,
321 doc
: /* Add a watch for filesystem events pertaining to FILE.
323 This arranges for filesystem events pertaining to FILE to be reported
324 to Emacs. Use `kqueue-rm-watch' to cancel the watch.
326 Returned value is a descriptor for the added watch. If the file cannot be
327 watched for some reason, this function signals a `file-notify-error' error.
329 FLAGS is a list of events to be watched for. It can include the
332 `create' -- FILE was created
333 `delete' -- FILE was deleted
334 `write' -- FILE has changed
335 `extend' -- FILE was extended
336 `attrib' -- a FILE attribute was changed
337 `link' -- a FILE's link count was changed
338 `rename' -- FILE was moved to FILE1
340 When any event happens, Emacs will call the CALLBACK function passing
341 it a single argument EVENT, which is of the form
343 (DESCRIPTOR ACTIONS FILE [FILE1])
345 DESCRIPTOR is the same object as the one returned by this function.
346 ACTIONS is a list of events.
348 FILE is the name of the file whose event is being reported. FILE1
349 will be reported only in case of the `rename' event. This is possible
350 only when the upper directory of the renamed file is watched. */)
351 (Lisp_Object file
, Lisp_Object flags
, Lisp_Object callback
)
353 Lisp_Object watch_object
, dir_list
;
358 /* Check parameters. */
360 file
= Fdirectory_file_name (Fexpand_file_name (file
, Qnil
));
361 if (NILP (Ffile_exists_p (file
)))
362 report_file_error ("File does not exist", file
);
366 if (! FUNCTIONP (callback
))
367 wrong_type_argument (Qinvalid_function
, callback
);
371 /* Create kqueue descriptor. */
372 kqueuefd
= kqueue ();
374 report_file_notify_error ("File watching is not available", Qnil
);
376 /* Start monitoring for possible I/O. */
377 add_read_fd (kqueuefd
, kqueue_callback
, NULL
);
383 file
= ENCODE_FILE (file
);
393 oflags
|= O_NOFOLLOW
;
395 fd
= emacs_open (SSDATA (file
), oflags
, 0);
397 report_file_error ("File cannot be opened", file
);
399 /* Assemble filter flags */
400 if (! NILP (Fmember (Qdelete
, flags
))) fflags
|= NOTE_DELETE
;
401 if (! NILP (Fmember (Qwrite
, flags
))) fflags
|= NOTE_WRITE
;
402 if (! NILP (Fmember (Qextend
, flags
))) fflags
|= NOTE_EXTEND
;
403 if (! NILP (Fmember (Qattrib
, flags
))) fflags
|= NOTE_ATTRIB
;
404 if (! NILP (Fmember (Qlink
, flags
))) fflags
|= NOTE_LINK
;
405 if (! NILP (Fmember (Qrename
, flags
))) fflags
|= NOTE_RENAME
;
407 /* Register event. */
408 EV_SET (&kev
, fd
, EVFILT_VNODE
, EV_ADD
| EV_ENABLE
| EV_CLEAR
,
411 if (kevent (kqueuefd
, &kev
, 1, NULL
, 0, NULL
) < 0) {
413 report_file_error ("Cannot watch file", file
);
416 /* Store watch object in watch list. */
417 Lisp_Object watch_descriptor
= make_number (fd
);
418 if (NILP (Ffile_directory_p (file
)))
419 watch_object
= list4 (watch_descriptor
, file
, flags
, callback
);
421 dir_list
= directory_files_internal (file
, Qnil
, Qnil
, Qnil
, 1, Qnil
);
422 watch_object
= list5 (watch_descriptor
, file
, flags
, callback
, dir_list
);
424 watch_list
= Fcons (watch_object
, watch_list
);
426 return watch_descriptor
;
429 DEFUN ("kqueue-rm-watch", Fkqueue_rm_watch
, Skqueue_rm_watch
, 1, 1, 0,
430 doc
: /* Remove an existing WATCH-DESCRIPTOR.
432 WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'. */)
433 (Lisp_Object watch_descriptor
)
435 Lisp_Object watch_object
= assq_no_quit (watch_descriptor
, watch_list
);
437 if (! CONSP (watch_object
))
438 xsignal2 (Qfile_notify_error
, build_string ("Not a watch descriptor"),
441 eassert (INTEGERP (watch_descriptor
));
442 int fd
= XINT (watch_descriptor
);
446 /* Remove watch descriptor from watch list. */
447 watch_list
= Fdelq (watch_object
, watch_list
);
449 if (NILP (watch_list
) && (kqueuefd
>= 0)) {
450 delete_read_fd (kqueuefd
);
451 emacs_close (kqueuefd
);
458 DEFUN ("kqueue-valid-p", Fkqueue_valid_p
, Skqueue_valid_p
, 1, 1, 0,
459 doc
: /* "Check a watch specified by its WATCH-DESCRIPTOR.
461 WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'.
463 A watch can become invalid if the file or directory it watches is
464 deleted, or if the watcher thread exits abnormally for any other
465 reason. Removing the watch by calling `kqueue-rm-watch' also makes it
467 (Lisp_Object watch_descriptor
)
469 return NILP (assq_no_quit (watch_descriptor
, watch_list
)) ? Qnil
: Qt
;
474 globals_of_kqueue (void)
480 syms_of_kqueue (void)
482 defsubr (&Skqueue_add_watch
);
483 defsubr (&Skqueue_rm_watch
);
484 defsubr (&Skqueue_valid_p
);
487 DEFSYM (Qcreate
, "create");
488 DEFSYM (Qdelete
, "delete"); /* NOTE_DELETE */
489 DEFSYM (Qwrite
, "write"); /* NOTE_WRITE */
490 DEFSYM (Qextend
, "extend"); /* NOTE_EXTEND */
491 DEFSYM (Qattrib
, "attrib"); /* NOTE_ATTRIB */
492 DEFSYM (Qlink
, "link"); /* NOTE_LINK */
493 DEFSYM (Qrename
, "rename"); /* NOTE_RENAME */
495 staticpro (&watch_list
);
497 Fprovide (intern_c_string ("kqueue"), Qnil
);
500 #endif /* HAVE_KQUEUE */
503 * https://bugs.launchpad.net/ubuntu/+source/libkqueue/+bug/1514837
504 prevents tests on Ubuntu. */