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