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 temporary 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
;
44 for (dl
= directory_files
; ! NILP (dl
); dl
= XCDR (dl
)) {
47 XCAR (Fnthcdr (make_number (11), XCAR (dl
))),
50 /* last modification time. */
51 XCAR (Fnthcdr (make_number (6), XCAR (dl
))),
52 /* last status change time. */
53 XCAR (Fnthcdr (make_number (7), XCAR (dl
))),
55 XCAR (Fnthcdr (make_number (8), XCAR (dl
)))),
61 /* Generate a file notification event. */
64 (Lisp_Object ident
, Lisp_Object actions
, Lisp_Object file
, Lisp_Object file1
, Lisp_Object callback
)
66 struct input_event event
;
68 event
.kind
= FILE_NOTIFY_EVENT
;
69 event
.frame_or_window
= Qnil
;
70 event
.arg
= list2 (Fcons (ident
, Fcons (actions
,
73 : list2 (file
, file1
))),
76 /* Store it into the input event queue. */
77 kbd_buffer_store_event (&event
);
80 /* This compares two directory listings in case of a `write' event for
81 a directory. The old directory listing is stored in watch_object,
82 it will be replaced by a new directory listing at the end of this
85 kqueue_compare_dir_list
86 (Lisp_Object watch_object
)
88 Lisp_Object dir
, callback
, actions
;
89 Lisp_Object old_directory_files
, old_dl
, new_directory_files
, new_dl
, dl
;
91 dir
= XCAR (XCDR (watch_object
));
92 callback
= XCAR (Fnthcdr (make_number (3), watch_object
));
93 old_directory_files
= XCAR (Fnthcdr (make_number (4), watch_object
));
94 old_dl
= kqueue_directory_listing (old_directory_files
);
96 directory_files_internal (dir
, Qnil
, Qnil
, Qnil
, 1, Qnil
);
97 new_dl
= kqueue_directory_listing (new_directory_files
);
99 /* Parse through the old list. */
102 Lisp_Object old_entry
, new_entry
;
106 /* We ignore "." and "..". */
107 old_entry
= XCAR (dl
);
108 if ((strcmp (".", SSDATA (XCAR (XCDR (old_entry
)))) == 0) ||
109 (strcmp ("..", SSDATA (XCAR (XCDR (old_entry
)))) == 0))
112 /* Search for an entry with the same inode. */
113 new_entry
= Fassoc (XCAR (old_entry
), new_dl
);
114 if (! NILP (Fequal (old_entry
, new_entry
))) {
115 /* Both entries are identical. Nothing happens. */
116 new_dl
= Fdelq (new_entry
, new_dl
);
120 if (! NILP (new_entry
)) {
121 /* Both entries have the same inode. */
122 if (strcmp (SSDATA (XCAR (XCDR (old_entry
))),
123 SSDATA (XCAR (XCDR (new_entry
)))) == 0) {
124 /* Both entries have the same file name. */
125 if (! NILP (Fequal (XCAR (Fnthcdr (make_number (2), old_entry
)),
126 XCAR (Fnthcdr (make_number (2), new_entry
)))))
127 /* Modification time has been changed, the file has been written. */
128 kqueue_generate_event
129 (XCAR (watch_object
), Fcons (Qwrite
, Qnil
),
130 XCAR (XCDR (old_entry
)), Qnil
, callback
);
131 if (! NILP (Fequal (XCAR (Fnthcdr (make_number (3), old_entry
)),
132 XCAR (Fnthcdr (make_number (3), new_entry
)))))
133 /* Status change time has been changed, the file attributes
135 kqueue_generate_event
136 (XCAR (watch_object
), Fcons (Qattrib
, Qnil
),
137 XCAR (XCDR (old_entry
)), Qnil
, callback
);
140 /* The file has been renamed. */
141 kqueue_generate_event
142 (XCAR (watch_object
), Fcons (Qrename
, Qnil
),
143 XCAR (XCDR (old_entry
)), XCAR (XCDR (new_entry
)), callback
);
145 new_dl
= Fdelq (new_entry
, new_dl
);
149 /* Search, whether there is a file with the same name (with
152 for (dl1
= new_dl
; ! NILP (dl1
); dl1
= XCDR (dl1
)) {
153 new_entry
= XCAR (dl1
);
154 if (strcmp (SSDATA (XCAR (XCDR (old_entry
))),
155 SSDATA (XCAR (XCDR (new_entry
)))) == 0) {
156 kqueue_generate_event
157 (XCAR (watch_object
), Fcons (Qwrite
, Qnil
),
158 XCAR (XCDR (old_entry
)), Qnil
, callback
);
159 new_dl
= Fdelq (new_entry
, new_dl
);
164 /* A file has been deleted. */
165 kqueue_generate_event
166 (XCAR (watch_object
), Fcons (Qdelete
, Qnil
),
167 XCAR (XCDR (old_entry
)), Qnil
, callback
);
171 old_dl
= Fdelq (old_entry
, old_dl
);
174 /* Parse through the shortened new list. */
177 Lisp_Object new_entry
;
181 /* We ignore "." and "..". */
182 new_entry
= XCAR (dl
);
183 if ((strcmp (".", SSDATA (XCAR (XCDR (new_entry
)))) == 0) ||
184 (strcmp ("..", SSDATA (XCAR (XCDR (new_entry
)))) == 0)) {
186 new_dl
= Fdelq (new_entry
, new_dl
);
190 /* A new file has appeared. */
191 kqueue_generate_event
192 (XCAR (watch_object
), Fcons (Qcreate
, Qnil
),
193 XCAR (XCDR (new_entry
)), Qnil
, callback
);
195 /* Check size of that file. */
196 Lisp_Object size
= XCAR (Fnthcdr (make_number (4), new_entry
));
197 if (FLOATP (size
) || (XINT (size
) > 0))
198 kqueue_generate_event
199 (XCAR (watch_object
), Fcons (Qwrite
, Qnil
),
200 XCAR (XCDR (new_entry
)), Qnil
, callback
);
203 new_dl
= Fdelq (new_entry
, new_dl
);
206 /* At this point, both old_dl and new_dl shall be empty. Let's make
207 a check for this (might be removed once the code is stable). */
209 report_file_error ("Old list not empty", old_dl
);
211 report_file_error ("New list not empty", new_dl
);
213 /* Replace directory listing with the new one. */
214 XSETCDR (XCDR (XCDR (XCDR (watch_object
))),
215 Fcons (new_directory_files
, Qnil
));
219 /* This is the callback function for arriving input on kqueuefd. It
220 shall create a Lisp event, and put it into Emacs input queue. */
222 kqueue_callback (int fd
, void *data
)
226 static const struct timespec nullts
= { 0, 0 };
227 Lisp_Object monitor_object
, watch_object
, file
, callback
, dirp
, actions
;
229 /* Read one event. */
230 int ret
= kevent (kqueuefd
, NULL
, 0, &kev
, 1, &nullts
);
232 /* All events read. */
236 /* Determine file name and callback function. */
237 monitor_object
= make_number (kev
.ident
);
238 watch_object
= assq_no_quit (monitor_object
, watch_list
);
240 if (CONSP (watch_object
)) {
241 file
= XCAR (XCDR (watch_object
));
242 callback
= XCAR (XCDR (XCDR (XCDR (watch_object
))));
243 dirp
= XCDR (XCDR (XCDR (XCDR (watch_object
))));
248 /* Determine event actions. */
250 if (kev
.fflags
& NOTE_DELETE
)
251 actions
= Fcons (Qdelete
, actions
);
252 if (kev
.fflags
& NOTE_WRITE
) {
254 actions
= Fcons (Qwrite
, actions
);
256 kqueue_compare_dir_list (watch_object
);
258 if (kev
.fflags
& NOTE_EXTEND
)
259 actions
= Fcons (Qextend
, actions
);
260 if (kev
.fflags
& NOTE_ATTRIB
)
261 actions
= Fcons (Qattrib
, actions
);
262 if (kev
.fflags
& NOTE_LINK
)
263 actions
= Fcons (Qlink
, actions
);
264 if (kev
.fflags
& NOTE_RENAME
)
265 actions
= Fcons (Qrename
, actions
);
267 /* Construct an event. */
268 if (! NILP (actions
))
269 kqueue_generate_event (monitor_object
, actions
, file
, Qnil
, callback
);
271 /* Cancel monitor if file or directory is deleted. */
272 if (kev
.fflags
& (NOTE_DELETE
| NOTE_RENAME
))
273 Fkqueue_rm_watch (monitor_object
);
278 DEFUN ("kqueue-add-watch", Fkqueue_add_watch
, Skqueue_add_watch
, 3, 3, 0,
279 doc
: /* Add a watch for filesystem events pertaining to FILE.
281 This arranges for filesystem events pertaining to FILE to be reported
282 to Emacs. Use `kqueue-rm-watch' to cancel the watch.
284 Returned value is a descriptor for the added watch. If the file cannot be
285 watched for some reason, this function signals a `file-notify-error' error.
287 FLAGS is a list of events to be watched for. It can include the
290 `create' -- FILE was created
291 `delete' -- FILE was deleted
292 `write' -- FILE has changed
293 `extend' -- FILE was extended
294 `attrib' -- a FILE attribute was changed
295 `link' -- a FILE's link count was changed
296 `rename' -- FILE was moved to FILE1
298 When any event happens, Emacs will call the CALLBACK function passing
299 it a single argument EVENT, which is of the form
301 (DESCRIPTOR ACTIONS FILE [FILE1])
303 DESCRIPTOR is the same object as the one returned by this function.
304 ACTIONS is a list of events.
306 FILE is the name of the file whose event is being reported. FILE1
307 will be reported only in case of the `rename' event. */)
308 (Lisp_Object file
, Lisp_Object flags
, Lisp_Object callback
)
310 Lisp_Object watch_object
, dir_list
;
315 /* Check parameters. */
317 file
= Fdirectory_file_name (Fexpand_file_name (file
, Qnil
));
318 if (NILP (Ffile_exists_p (file
)))
319 report_file_error ("File does not exist", file
);
323 if (! FUNCTIONP (callback
))
324 wrong_type_argument (Qinvalid_function
, callback
);
328 /* Create kqueue descriptor. */
329 kqueuefd
= kqueue ();
331 report_file_notify_error ("File watching is not available", Qnil
);
333 /* Start monitoring for possible I/O. */
334 add_read_fd (kqueuefd
, kqueue_callback
, NULL
);
340 file
= ENCODE_FILE (file
);
341 fd
= emacs_open (SSDATA (file
), O_RDONLY
, 0);
343 report_file_error ("File cannot be opened", file
);
345 /* Assemble filter flags */
346 if (! NILP (Fmember (Qdelete
, flags
))) fflags
|= NOTE_DELETE
;
347 if (! NILP (Fmember (Qwrite
, flags
))) fflags
|= NOTE_WRITE
;
348 if (! NILP (Fmember (Qextend
, flags
))) fflags
|= NOTE_EXTEND
;
349 if (! NILP (Fmember (Qattrib
, flags
))) fflags
|= NOTE_ATTRIB
;
350 if (! NILP (Fmember (Qlink
, flags
))) fflags
|= NOTE_LINK
;
351 if (! NILP (Fmember (Qrename
, flags
))) fflags
|= NOTE_RENAME
;
353 /* Register event. */
354 EV_SET (&ev
, fd
, EVFILT_VNODE
, EV_ADD
| EV_ENABLE
| EV_CLEAR
,
357 if (kevent (kqueuefd
, &ev
, 1, NULL
, 0, NULL
) < 0) {
359 report_file_error ("Cannot watch file", file
);
362 /* Store watch object in watch list. */
363 Lisp_Object watch_descriptor
= make_number (fd
);
364 if (NILP (Ffile_directory_p (file
)))
365 watch_object
= list4 (watch_descriptor
, file
, flags
, callback
);
367 dir_list
= directory_files_internal (file
, Qnil
, Qnil
, Qnil
, 1, Qnil
);
368 watch_object
= list5 (watch_descriptor
, file
, flags
, callback
, dir_list
);
370 watch_list
= Fcons (watch_object
, watch_list
);
372 return watch_descriptor
;
375 DEFUN ("kqueue-rm-watch", Fkqueue_rm_watch
, Skqueue_rm_watch
, 1, 1, 0,
376 doc
: /* Remove an existing WATCH-DESCRIPTOR.
378 WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'. */)
379 (Lisp_Object watch_descriptor
)
381 Lisp_Object watch_object
= assq_no_quit (watch_descriptor
, watch_list
);
383 if (! CONSP (watch_object
))
384 xsignal2 (Qfile_notify_error
, build_string ("Not a watch descriptor"),
387 eassert (INTEGERP (watch_descriptor
));
388 int fd
= XINT (watch_descriptor
);
392 /* Remove watch descriptor from watch list. */
393 watch_list
= Fdelq (watch_object
, watch_list
);
395 if (NILP (watch_list
) && (kqueuefd
>= 0)) {
396 delete_read_fd (kqueuefd
);
397 emacs_close (kqueuefd
);
404 DEFUN ("kqueue-valid-p", Fkqueue_valid_p
, Skqueue_valid_p
, 1, 1, 0,
405 doc
: /* "Check a watch specified by its WATCH-DESCRIPTOR.
407 WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'.
409 A watch can become invalid if the file or directory it watches is
410 deleted, or if the watcher thread exits abnormally for any other
411 reason. Removing the watch by calling `kqueue-rm-watch' also makes it
413 (Lisp_Object watch_descriptor
)
415 return NILP (assq_no_quit (watch_descriptor
, watch_list
)) ? Qnil
: Qt
;
420 globals_of_kqueue (void)
426 syms_of_kqueue (void)
428 defsubr (&Skqueue_add_watch
);
429 defsubr (&Skqueue_rm_watch
);
430 defsubr (&Skqueue_valid_p
);
433 DEFSYM (Qcreate
, "create");
434 DEFSYM (Qdelete
, "delete"); /* NOTE_DELETE */
435 DEFSYM (Qwrite
, "write"); /* NOTE_WRITE */
436 DEFSYM (Qextend
, "extend"); /* NOTE_EXTEND */
437 DEFSYM (Qattrib
, "attrib"); /* NOTE_ATTRIB */
438 DEFSYM (Qlink
, "link"); /* NOTE_LINK */
439 DEFSYM (Qrename
, "rename"); /* NOTE_RENAME */
441 staticpro (&watch_list
);
443 Fprovide (intern_c_string ("kqueue"), Qnil
);
446 #endif /* HAVE_KQUEUE */
449 * https://bugs.launchpad.net/ubuntu/+source/libkqueue/+bug/1514837
450 prevents tests on Ubuntu. */