]> code.delx.au - gnu-emacs/blob - src/kqueue.c
Continue kqueue implementation
[gnu-emacs] / src / kqueue.c
1 /* Filesystem notifications support with glib API.
2 Copyright (C) 2013-2015 Free Software Foundation, Inc.
3
4 This file is part of GNU Emacs.
5
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.
10
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.
15
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/>. */
18
19 #include <config.h>
20
21 #ifdef HAVE_KQUEUE
22 #include <stdio.h>
23 #include <sys/types.h>
24 #include <sys/event.h>
25 #include <sys/file.h>
26 #include "lisp.h"
27 #include "keyboard.h"
28 #include "process.h"
29
30 \f
31 /* File handle for kqueue. */
32 static int kqueuefd = -1;
33
34 /* This is a list, elements are triples (DESCRIPTOR FILE FLAGS CALLBACK) */
35 static Lisp_Object watch_list;
36
37 /* This is the callback function for arriving input on kqueuefd. It
38 shall create a Lisp event, and put it into Emacs input queue. */
39 static void
40 kqueue_callback (int fd, void *data)
41 {
42 for (;;) {
43 struct kevent kev;
44 struct input_event event;
45 Lisp_Object monitor_object, watch_object, file, callback, actions;
46
47 /* Read one event. */
48 int ret = kevent (kqueuefd, NULL, 0, &kev, 1, NULL);
49 if (ret < 1) {
50 /* All events read. */
51 return;
52 }
53
54 /* Determine file name and callback function. */
55 monitor_object = make_number (kev.ident);
56 watch_object = assq_no_quit (monitor_object, watch_list);
57
58 if (CONSP (watch_object)) {
59 file = XCAR (XCDR (watch_object));
60 callback = XCAR (XCDR (XCDR (XCDR (watch_object))));
61 }
62 else
63 continue;
64
65 /* Determine event actions. */
66 actions = Qnil;
67 if (kev.fflags & NOTE_DELETE)
68 actions = Fcons (Qdelete, actions);
69 if (kev.fflags & NOTE_WRITE)
70 actions = Fcons (Qwrite, actions);
71 if (kev.fflags & NOTE_EXTEND)
72 actions = Fcons (Qextend, actions);
73 if (kev.fflags & NOTE_ATTRIB)
74 actions = Fcons (Qattrib, actions);
75 if (kev.fflags & NOTE_LINK)
76 actions = Fcons (Qlink, actions);
77 if (kev.fflags & NOTE_RENAME)
78 actions = Fcons (Qrename, actions);
79
80 if (! NILP (actions)) {
81 /* Construct an event. */
82 EVENT_INIT (event);
83 event.kind = FILE_NOTIFY_EVENT;
84 event.frame_or_window = Qnil;
85 event.arg = list2 (Fcons (monitor_object,
86 Fcons (actions, Fcons (file, Qnil))),
87 callback);
88
89 /* Store it into the input event queue. */
90 kbd_buffer_store_event (&event);
91 }
92
93 /* Cancel monitor if file or directory is deleted. */
94 if (kev.fflags & (NOTE_DELETE | NOTE_RENAME))
95 Fkqueue_rm_watch (monitor_object);
96 }
97 return;
98 }
99
100 DEFUN ("kqueue-add-watch", Fkqueue_add_watch, Skqueue_add_watch, 3, 3, 0,
101 doc: /* Add a watch for filesystem events pertaining to FILE.
102
103 This arranges for filesystem events pertaining to FILE to be reported
104 to Emacs. Use `kqueue-rm-watch' to cancel the watch.
105
106 Returned value is a descriptor for the added watch. If the file cannot be
107 watched for some reason, this function signals a `file-notify-error' error.
108
109 FLAGS is a list of events to be watched for. It can include the
110 following symbols:
111
112 `delete' -- FILE was deleted
113 `write' -- FILE has changed
114 `extend' -- FILE was extended
115 `attrib' -- a FILE attribute was changed
116 `link' -- a FILE's link count was changed
117 `rename' -- FILE was moved to FILE1
118
119 When any event happens, Emacs will call the CALLBACK function passing
120 it a single argument EVENT, which is of the form
121
122 (DESCRIPTOR ACTIONS FILE [FILE1])
123
124 DESCRIPTOR is the same object as the one returned by this function.
125 ACTIONS is a list of events.
126
127 FILE is the name of the file whose event is being reported. FILE1
128 will be reported only in case of the `rename' event. */)
129 (Lisp_Object file, Lisp_Object flags, Lisp_Object callback)
130 {
131 Lisp_Object watch_object;
132 int fd;
133 u_short fflags = 0;
134 struct kevent ev;
135
136 /* Check parameters. */
137 CHECK_STRING (file);
138 file = Fdirectory_file_name (Fexpand_file_name (file, Qnil));
139 if (NILP (Ffile_exists_p (file)))
140 report_file_error ("File does not exist", file);
141
142 /* TODO: Directories shall be supported as well. */
143 if (! NILP (Ffile_directory_p (file)))
144 report_file_error ("Directory watching is not supported (yet)", file);
145
146 CHECK_LIST (flags);
147
148 if (! FUNCTIONP (callback))
149 wrong_type_argument (Qinvalid_function, callback);
150
151 if (kqueuefd < 0)
152 {
153 /* Create kqueue descriptor. */
154 kqueuefd = kqueue ();
155 if (kqueuefd < 0)
156 report_file_notify_error ("File watching is not available", Qnil);
157
158 /* Start monitoring for possible I/O. */
159 add_read_fd (kqueuefd, kqueue_callback, NULL); //data);
160
161 watch_list = Qnil;
162 }
163
164 /* Open file. */
165 file = ENCODE_FILE (file);
166 fd = emacs_open (SSDATA (file), O_NONBLOCK | O_BINARY | O_RDONLY, 0);
167 if (fd == -1)
168 report_file_error ("File cannot be opened", file);
169
170 /* Assemble filter flags */
171 if (! NILP (Fmember (Qdelete, flags))) fflags |= NOTE_DELETE;
172 if (! NILP (Fmember (Qwrite, flags))) fflags |= NOTE_WRITE;
173 if (! NILP (Fmember (Qextend, flags))) fflags |= NOTE_EXTEND;
174 if (! NILP (Fmember (Qattrib, flags))) fflags |= NOTE_ATTRIB;
175 if (! NILP (Fmember (Qlink, flags))) fflags |= NOTE_LINK;
176 if (! NILP (Fmember (Qrename, flags))) fflags |= NOTE_RENAME;
177
178 /* Register event. */
179 EV_SET (&ev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR,
180 fflags, 0, NULL);
181
182 if (kevent (kqueuefd, &ev, 1, NULL, 0, NULL) < 0)
183 report_file_error ("Cannot watch file", file);
184
185 /* Store watch object in watch list. */
186 Lisp_Object watch_descriptor = make_number (fd);
187 watch_object = list4 (watch_descriptor, file, flags, callback);
188 watch_list = Fcons (watch_object, watch_list);
189
190 return watch_descriptor;
191 }
192
193 DEFUN ("kqueue-rm-watch", Fkqueue_rm_watch, Skqueue_rm_watch, 1, 1, 0,
194 doc: /* Remove an existing WATCH-DESCRIPTOR.
195
196 WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'. */)
197 (Lisp_Object watch_descriptor)
198 {
199 Lisp_Object watch_object = assq_no_quit (watch_descriptor, watch_list);
200
201 if (! CONSP (watch_object))
202 xsignal2 (Qfile_notify_error, build_string ("Not a watch descriptor"),
203 watch_descriptor);
204
205 eassert (INTEGERP (watch_descriptor));
206 int fd = XINT (watch_descriptor);
207 if ( fd >= 0)
208 emacs_close (fd);
209
210 /* Remove watch descriptor from watch list. */
211 watch_list = Fdelq (watch_object, watch_list);
212
213 if (NILP (watch_list) && (kqueuefd >= 0)) {
214 delete_read_fd (kqueuefd);
215 emacs_close (kqueuefd);
216 kqueuefd = -1;
217 }
218
219 return Qt;
220 }
221
222 DEFUN ("kqueue-valid-p", Fkqueue_valid_p, Skqueue_valid_p, 1, 1, 0,
223 doc: /* "Check a watch specified by its WATCH-DESCRIPTOR.
224
225 WATCH-DESCRIPTOR should be an object returned by `kqueue-add-watch'.
226
227 A watch can become invalid if the file or directory it watches is
228 deleted, or if the watcher thread exits abnormally for any other
229 reason. Removing the watch by calling `kqueue-rm-watch' also makes it
230 invalid. */)
231 (Lisp_Object watch_descriptor)
232 {
233 return NILP (assq_no_quit (watch_descriptor, watch_list)) ? Qnil : Qt;
234 }
235
236 \f
237 void
238 globals_of_kqueue (void)
239 {
240 watch_list = Qnil;
241 }
242
243 void
244 syms_of_kqueue (void)
245 {
246 defsubr (&Skqueue_add_watch);
247 defsubr (&Skqueue_rm_watch);
248 defsubr (&Skqueue_valid_p);
249
250 /* Event types. */
251 DEFSYM (Qdelete, "delete"); /* NOTE_DELETE */
252 DEFSYM (Qwrite, "write"); /* NOTE_WRITE */
253 DEFSYM (Qextend, "extend"); /* NOTE_EXTEND */
254 DEFSYM (Qattrib, "attrib"); /* NOTE_ATTRIB */
255 DEFSYM (Qlink, "link"); /* NOTE_LINK */
256 DEFSYM (Qrename, "rename"); /* NOTE_RENAME */
257
258 staticpro (&watch_list);
259
260 Fprovide (intern_c_string ("kqueue"), Qnil);
261 }
262
263 #endif /* HAVE_KQUEUE */
264
265 /* TODO
266 * Implement watching directories.
267 * Add FILE1 in case of `rename'. */
268
269 /* PROBLEMS
270 * https://bugs.launchpad.net/ubuntu/+source/libkqueue/+bug/1514837
271 prevents tests on Ubuntu. */