Code cleanup of kqueue.c
[emacs.git] / src / kqueue.c
blob5caef67b92a2fea61957dbe98c66da21a15fde1b
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/>. */
19 #include <config.h>
21 #ifdef HAVE_KQUEUE
22 #include <stdio.h>
23 #include <sys/types.h>
24 #include <sys/event.h>
25 #include <sys/time.h>
26 #include <sys/file.h>
27 #include "lisp.h"
28 #include "keyboard.h"
29 #include "process.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). */
40 Lisp_Object
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))
49 continue;
51 result = Fcons
52 (list5 (/* inode. */
53 Fnth (make_number (11), XCAR (dl)),
54 /* filename. */
55 XCAR (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)),
60 /* size. */
61 Fnth (make_number (8), XCAR (dl))),
62 result);
64 return result;
67 /* Generate a file notification event. */
68 static void
69 kqueue_generate_event
70 (Lisp_Object ident, Lisp_Object actions, Lisp_Object file, Lisp_Object file1,
71 Lisp_Object callback)
73 struct input_event event;
74 EVENT_INIT (event);
75 event.kind = FILE_NOTIFY_EVENT;
76 event.frame_or_window = Qnil;
77 event.arg = list2 (Fcons (ident, Fcons (actions,
78 NILP (file1)
79 ? Fcons (file, Qnil)
80 : list2 (file, file1))),
81 callback);
83 /* Store it into the input event queue. */
84 kbd_buffer_store_event (&event);
87 /* This compares two directory listings in case of a `write' event for
88 a directory. Generate resulting file notification events. The old
89 directory listing is retrieved from watch_object, it will be
90 replaced by the new directory listing at the end of this
91 function. */
92 static void
93 kqueue_compare_dir_list
94 (Lisp_Object watch_object)
96 Lisp_Object dir, callback;
97 Lisp_Object old_directory_files, old_dl, new_directory_files, new_dl, dl;
99 dir = XCAR (XCDR (watch_object));
100 callback = Fnth (make_number (3), watch_object);
102 old_directory_files = Fnth (make_number (4), watch_object);
103 old_dl = kqueue_directory_listing (old_directory_files);
105 /* When the directory is not accessible anymore, it has been deleted. */
106 if (NILP (Ffile_directory_p (dir))) {
107 kqueue_generate_event
108 (XCAR (watch_object), Fcons (Qdelete, Qnil), dir, Qnil, callback);
109 return;
111 new_directory_files =
112 directory_files_internal (dir, Qnil, Qnil, Qnil, 1, Qnil);
113 new_dl = kqueue_directory_listing (new_directory_files);
115 /* Parse through the old list. */
116 dl = old_dl;
117 while (1) {
118 Lisp_Object old_entry, new_entry, dl1;
119 if (NILP (dl))
120 break;
122 /* Search for an entry with the same inode. */
123 old_entry = XCAR (dl);
124 new_entry = Fassoc (XCAR (old_entry), new_dl);
125 if (! NILP (Fequal (old_entry, new_entry))) {
126 /* Both entries are identical. Nothing to do. */
127 new_dl = Fdelq (new_entry, new_dl);
128 goto the_end;
131 /* Both entries have the same inode. */
132 if (! NILP (new_entry)) {
133 /* Both entries have the same file name. */
134 if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
135 SSDATA (XCAR (XCDR (new_entry)))) == 0) {
136 /* Modification time has been changed, the file has been written. */
137 if (NILP (Fequal (Fnth (make_number (2), old_entry),
138 Fnth (make_number (2), new_entry))))
139 kqueue_generate_event
140 (XCAR (watch_object), Fcons (Qwrite, Qnil),
141 XCAR (XCDR (old_entry)), Qnil, callback);
142 /* Status change time has been changed, the file attributes
143 have changed. */
144 if (NILP (Fequal (Fnth (make_number (3), old_entry),
145 Fnth (make_number (3), new_entry))))
146 kqueue_generate_event
147 (XCAR (watch_object), Fcons (Qattrib, Qnil),
148 XCAR (XCDR (old_entry)), Qnil, callback);
150 } else {
151 /* The file has been renamed. */
152 kqueue_generate_event
153 (XCAR (watch_object), Fcons (Qrename, Qnil),
154 XCAR (XCDR (old_entry)), XCAR (XCDR (new_entry)), callback);
156 new_dl = Fdelq (new_entry, new_dl);
157 goto the_end;
160 /* Search, whether there is a file with the same name but another
161 inode. */
162 for (dl1 = new_dl; ! NILP (dl1); dl1 = XCDR (dl1)) {
163 new_entry = XCAR (dl1);
164 if (strcmp (SSDATA (XCAR (XCDR (old_entry))),
165 SSDATA (XCAR (XCDR (new_entry)))) == 0) {
166 kqueue_generate_event
167 (XCAR (watch_object), Fcons (Qwrite, Qnil),
168 XCAR (XCDR (old_entry)), Qnil, callback);
169 new_dl = Fdelq (new_entry, new_dl);
170 goto the_end;
174 /* The file has been deleted. */
175 kqueue_generate_event
176 (XCAR (watch_object), Fcons (Qdelete, Qnil),
177 XCAR (XCDR (old_entry)), Qnil, callback);
179 the_end:
180 dl = XCDR (dl);
181 old_dl = Fdelq (old_entry, old_dl);
184 /* Parse through the resulting new list. */
185 dl = new_dl;
186 while (1) {
187 Lisp_Object new_entry;
188 if (NILP (dl))
189 break;
191 /* A new file has appeared. */
192 new_entry = XCAR (dl);
193 kqueue_generate_event
194 (XCAR (watch_object), Fcons (Qcreate, Qnil),
195 XCAR (XCDR (new_entry)), Qnil, callback);
197 /* Check size of that file. */
198 Lisp_Object size = Fnth (make_number (4), new_entry);
199 if (FLOATP (size) || (XINT (size) > 0))
200 kqueue_generate_event
201 (XCAR (watch_object), Fcons (Qwrite, Qnil),
202 XCAR (XCDR (new_entry)), Qnil, callback);
204 dl = XCDR (dl);
205 new_dl = Fdelq (new_entry, new_dl);
208 /* At this point, both old_dl and new_dl shall be empty. Let's make
209 a check for this (might be removed once the code is stable). */
210 if (! NILP (old_dl))
211 report_file_error ("Old list not empty", old_dl);
212 if (! NILP (new_dl))
213 report_file_error ("New list not empty", new_dl);
215 /* Replace old directory listing with the new one. */
216 XSETCDR (Fnthcdr (make_number (3), watch_object),
217 Fcons (new_directory_files, Qnil));
218 return;
221 /* This is the callback function for arriving input on kqueuefd. It
222 shall create a Lisp event, and put it into the Emacs input queue. */
223 static void
224 kqueue_callback (int fd, void *data)
226 for (;;) {
227 struct kevent kev;
228 static const struct timespec nullts = { 0, 0 };
229 Lisp_Object descriptor, watch_object, file, callback, actions;
231 /* Read one event. */
232 int ret = kevent (kqueuefd, NULL, 0, &kev, 1, &nullts);
233 if (ret < 1) {
234 /* All events read. */
235 return;
238 /* Determine descriptor, file name and callback function. */
239 descriptor = make_number (kev.ident);
240 watch_object = assq_no_quit (descriptor, watch_list);
242 if (CONSP (watch_object)) {
243 file = XCAR (XCDR (watch_object));
244 callback = Fnth (make_number (3), watch_object);
246 else
247 continue;
249 /* Determine event actions. */
250 actions = Qnil;
251 if (kev.fflags & NOTE_DELETE)
252 actions = Fcons (Qdelete, actions);
253 if (kev.fflags & NOTE_WRITE) {
254 /* Check, whether this is a directory event. */
255 if (NILP (Fnth (make_number (4), watch_object)))
256 actions = Fcons (Qwrite, actions);
257 else
258 kqueue_compare_dir_list (watch_object);
260 if (kev.fflags & NOTE_EXTEND)
261 actions = Fcons (Qextend, actions);
262 if (kev.fflags & NOTE_ATTRIB)
263 actions = Fcons (Qattrib, actions);
264 if (kev.fflags & NOTE_LINK)
265 actions = Fcons (Qlink, actions);
266 /* It would be useful to know the target of the rename operation.
267 At this point, it is not possible. Happens only when the upper
268 directory is monitored. */
269 if (kev.fflags & NOTE_RENAME)
270 actions = Fcons (Qrename, actions);
272 /* Create the event. */
273 if (! NILP (actions))
274 kqueue_generate_event (descriptor, actions, file, Qnil, callback);
276 /* Cancel monitor if file or directory is deleted or renamed. */
277 if (kev.fflags & (NOTE_DELETE | NOTE_RENAME))
278 Fkqueue_rm_watch (descriptor);
280 return;
283 DEFUN ("kqueue-add-watch", Fkqueue_add_watch, Skqueue_add_watch, 3, 3, 0,
284 doc: /* Add a watch for filesystem events pertaining to FILE.
286 This arranges for filesystem events pertaining to FILE to be reported
287 to Emacs. Use `kqueue-rm-watch' to cancel the watch.
289 Returned value is a descriptor for the added watch. If the file cannot be
290 watched for some reason, this function signals a `file-notify-error' error.
292 FLAGS is a list of events to be watched for. It can include the
293 following symbols:
295 `create' -- FILE was created
296 `delete' -- FILE was deleted
297 `write' -- FILE has changed
298 `extend' -- FILE was extended
299 `attrib' -- a FILE attribute was changed
300 `link' -- a FILE's link count was changed
301 `rename' -- FILE was moved to FILE1
303 When any event happens, Emacs will call the CALLBACK function passing
304 it a single argument EVENT, which is of the form
306 (DESCRIPTOR ACTIONS FILE [FILE1])
308 DESCRIPTOR is the same object as the one returned by this function.
309 ACTIONS is a list of events.
311 FILE is the name of the file whose event is being reported. FILE1
312 will be reported only in case of the `rename' event. This is possible
313 only when the upper directory of the renamed file is watched. */)
314 (Lisp_Object file, Lisp_Object flags, Lisp_Object callback)
316 Lisp_Object watch_object, dir_list;
317 int fd, oflags;
318 u_short fflags = 0;
319 struct kevent kev;
321 /* Check parameters. */
322 CHECK_STRING (file);
323 file = Fdirectory_file_name (Fexpand_file_name (file, Qnil));
324 if (NILP (Ffile_exists_p (file)))
325 report_file_error ("File does not exist", file);
327 CHECK_LIST (flags);
329 if (! FUNCTIONP (callback))
330 wrong_type_argument (Qinvalid_function, callback);
332 if (kqueuefd < 0)
334 /* Create kqueue descriptor. */
335 kqueuefd = kqueue ();
336 if (kqueuefd < 0)
337 report_file_notify_error ("File watching is not available", Qnil);
339 /* Start monitoring for possible I/O. */
340 add_read_fd (kqueuefd, kqueue_callback, NULL);
342 watch_list = Qnil;
345 /* Open file. */
346 file = ENCODE_FILE (file);
347 oflags = O_NONBLOCK;
348 #if O_EVTONLY
349 oflags |= O_EVTONLY;
350 #else
351 oflags |= O_RDONLY;
352 #endif
353 #if O_SYMLINK
354 oflags |= O_SYMLINK;
355 #else
356 oflags |= O_NOFOLLOW;
357 #endif
358 fd = emacs_open (SSDATA (file), oflags, 0);
359 if (fd == -1)
360 report_file_error ("File cannot be opened", file);
362 /* Assemble filter flags */
363 if (! NILP (Fmember (Qdelete, flags))) fflags |= NOTE_DELETE;
364 if (! NILP (Fmember (Qwrite, flags))) fflags |= NOTE_WRITE;
365 if (! NILP (Fmember (Qextend, flags))) fflags |= NOTE_EXTEND;
366 if (! NILP (Fmember (Qattrib, flags))) fflags |= NOTE_ATTRIB;
367 if (! NILP (Fmember (Qlink, flags))) fflags |= NOTE_LINK;
368 if (! NILP (Fmember (Qrename, flags))) fflags |= NOTE_RENAME;
370 /* Register event. */
371 EV_SET (&kev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
372 fflags, 0, NULL);
374 if (kevent (kqueuefd, &kev, 1, NULL, 0, NULL) < 0) {
375 emacs_close (fd);
376 report_file_error ("Cannot watch file", file);
379 /* Store watch object in watch list. */
380 Lisp_Object watch_descriptor = make_number (fd);
381 if (NILP (Ffile_directory_p (file)))
382 watch_object = list4 (watch_descriptor, file, flags, callback);
383 else {
384 dir_list = directory_files_internal (file, Qnil, Qnil, Qnil, 1, Qnil);
385 watch_object = list5 (watch_descriptor, file, flags, callback, dir_list);
387 watch_list = Fcons (watch_object, watch_list);
389 return watch_descriptor;
392 DEFUN ("kqueue-rm-watch", Fkqueue_rm_watch, Skqueue_rm_watch, 1, 1, 0,
393 doc: /* Remove an existing WATCH-DESCRIPTOR.
395 WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'. */)
396 (Lisp_Object watch_descriptor)
398 Lisp_Object watch_object = assq_no_quit (watch_descriptor, watch_list);
400 if (! CONSP (watch_object))
401 xsignal2 (Qfile_notify_error, build_string ("Not a watch descriptor"),
402 watch_descriptor);
404 eassert (INTEGERP (watch_descriptor));
405 int fd = XINT (watch_descriptor);
406 if ( fd >= 0)
407 emacs_close (fd);
409 /* Remove watch descriptor from watch list. */
410 watch_list = Fdelq (watch_object, watch_list);
412 if (NILP (watch_list) && (kqueuefd >= 0)) {
413 delete_read_fd (kqueuefd);
414 emacs_close (kqueuefd);
415 kqueuefd = -1;
418 return Qt;
421 DEFUN ("kqueue-valid-p", Fkqueue_valid_p, Skqueue_valid_p, 1, 1, 0,
422 doc: /* "Check a watch specified by its WATCH-DESCRIPTOR.
424 WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'.
426 A watch can become invalid if the file or directory it watches is
427 deleted, or if the watcher thread exits abnormally for any other
428 reason. Removing the watch by calling `kqueue-rm-watch' also makes it
429 invalid. */)
430 (Lisp_Object watch_descriptor)
432 return NILP (assq_no_quit (watch_descriptor, watch_list)) ? Qnil : Qt;
436 void
437 globals_of_kqueue (void)
439 watch_list = Qnil;
442 void
443 syms_of_kqueue (void)
445 defsubr (&Skqueue_add_watch);
446 defsubr (&Skqueue_rm_watch);
447 defsubr (&Skqueue_valid_p);
449 /* Event types. */
450 DEFSYM (Qcreate, "create");
451 DEFSYM (Qdelete, "delete"); /* NOTE_DELETE */
452 DEFSYM (Qwrite, "write"); /* NOTE_WRITE */
453 DEFSYM (Qextend, "extend"); /* NOTE_EXTEND */
454 DEFSYM (Qattrib, "attrib"); /* NOTE_ATTRIB */
455 DEFSYM (Qlink, "link"); /* NOTE_LINK */
456 DEFSYM (Qrename, "rename"); /* NOTE_RENAME */
458 staticpro (&watch_list);
460 Fprovide (intern_c_string ("kqueue"), Qnil);
463 #endif /* HAVE_KQUEUE */
465 /* PROBLEMS
466 * https://bugs.launchpad.net/ubuntu/+source/libkqueue/+bug/1514837
467 prevents tests on Ubuntu. */