extconf: don't abort for rb_memerror and rb_io_close()
[sleepy_penguin.git] / ext / sleepy_penguin / inotify.c
blob00343557b7834e7c792eb3936a51ddde25ed0098
1 #ifdef HAVE_SYS_INOTIFY_H
2 #include "sleepy_penguin.h"
3 #include <sys/inotify.h>
4 #include <sys/ioctl.h>
5 #include "missing_inotify.h"
6 #if defined(RFILE) && defined(HAVE_ST_FD) && \
7 defined(HAVE_RB_THREAD_BLOCKING_REGION) && \
8 ! defined(HAVE_RB_THREAD_IO_BLOCKING_REGION)
9 # define NOGVL_CLOSE
10 #endif
12 static ID id_for_fd, id_inotify_buf, id_inotify_tmp, id_mask;
13 static VALUE cEvent, checks;
16 * call-seq:
17 * Inotify.new([flags]) -> Inotify IO object
19 * Flags may be any of the following as an Array of Symbols or Integer mask:
20 * - :NONBLOCK - sets the non-blocking flag on the descriptor watched.
21 * - :CLOEXEC - sets the close-on-exec flag
23 static VALUE s_new(int argc, VALUE *argv, VALUE klass)
25 VALUE _flags, rv;
26 int flags;
27 int fd;
29 rb_scan_args(argc, argv, "01", &_flags);
30 flags = rb_sp_get_flags(klass, _flags);
32 fd = inotify_init1(flags);
33 if (fd == -1) {
34 if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
35 rb_gc();
36 fd = inotify_init1(flags);
38 if (fd == -1)
39 rb_sys_fail("inotify_init1");
42 rv = rb_funcall(klass, id_for_fd, 1, INT2NUM(fd));
43 rb_ivar_set(rv, id_inotify_buf, rb_str_new(0, 128));
44 rb_ivar_set(rv, id_inotify_tmp, rb_ary_new());
46 return rv;
50 * call-seq:
51 * ino.add_watch(path, flags) -> Integer
53 * Adds a watch on an object specified by its +mask+, returns an unsigned
54 * Integer watch descriptor. +flags+ may be a mask of the following
55 * Inotify constants or array of their symbolic names.
57 * - :ACCESS - File was accessed (read) (*)
58 * - :ATTRIB - Metadata changed.
59 * - :CLOSE_WRITE - File opened for writing was closed (*)
60 * - :CLOSE_NOWRITE - File not opened for writing was closed (*)
61 * - :CREATE - File/directory created in watched directory (*)
62 * - :DELETE - File/directory deleted from watched directory (*)
63 * - :DELETE_SELF - Watched file/directory was itself deleted
64 * - :MODIFY - File was modified (*)
65 * - :MOVE_SELF - Watched file/directory was itself moved
66 * - :MOVED_FROM - File moved out of watched directory (*)
67 * - :MOVED_TO - File moved into watched directory (*)
68 * - :OPEN - File was opened (*)
70 * When monitoring a directory, the events marked with an asterisk (*)
71 * above can occur for files in the directory, in which case the name
72 * field in the Event structure identifies the name of the file in the
73 * directory.
75 * Shortcut flags:
77 * - :ALL_EVENTS - a bitmask of all the above events
78 * - :MOVE - :MOVED_FROM or :MOVED_TO
79 * - :CLOSE - :CLOSE_WRITE or :CLOSE_NOWRITE
81 * The following watch attributes may also be included in flags:
83 * - :DONT_FOLLOW - don't dereference symlinks (since Linux 2.6.15)
84 * - :EXCL_UNLINK - don't generate unlink events for children (since 2.6.36)
85 * - :MASK_ADD - add events to an existing watch mask if it exists
86 * - :ONESHOT - monitor for one event and then remove it from the watch
87 * - :ONLYDIR - only watch the pathname if it is a directory
89 static VALUE add_watch(VALUE self, VALUE path, VALUE vmask)
91 int fd = rb_sp_fileno(self);
92 const char *pathname = StringValueCStr(path);
93 uint32_t mask = rb_sp_get_uflags(self, vmask);
94 int rc = inotify_add_watch(fd, pathname, mask);
96 if (rc == -1) {
97 if (errno == ENOMEM) {
98 rb_gc();
99 rc = inotify_add_watch(fd, pathname, mask);
101 if (rc == -1)
102 rb_sys_fail("inotify_add_watch");
104 return UINT2NUM((uint32_t)rc);
108 * call-seq:
109 * ino.rm_watch(watch_descriptor) -> 0
111 * Removes a watch based on a watch descriptor Integer. The watch
112 * descriptor is a return value given by Inotify#add_watch
114 static VALUE rm_watch(VALUE self, VALUE vwd)
116 uint32_t wd = NUM2UINT(vwd);
117 int fd = rb_sp_fileno(self);
118 int rc = inotify_rm_watch(fd, wd);
120 if (rc == -1)
121 rb_sys_fail("inotify_rm_watch");
122 return INT2NUM(rc);
125 static size_t event_len(struct inotify_event *e)
127 return sizeof(struct inotify_event) + e->len;
130 static VALUE event_new(struct inotify_event *e)
132 VALUE wd = INT2NUM(e->wd);
133 VALUE mask = UINT2NUM(e->mask);
134 VALUE cookie = UINT2NUM(e->cookie);
135 VALUE name;
137 /* name may be zero-padded, so we do strlen() */
138 name = e->len ? rb_str_new(e->name, strlen(e->name)) : Qnil;
140 return rb_struct_new(cEvent, wd, mask, cookie, name);
143 struct inread_args {
144 int fd;
145 struct inotify_event *ptr;
146 long len;
149 static VALUE inread(void *ptr)
151 struct inread_args *args = ptr;
153 return (VALUE)read(args->fd, args->ptr, args->len);
157 * call-seq:
158 * ino.take([nonblock]) -> Inotify::Event or nil
160 * Returns the next Inotify::Event processed. May return +nil+ if +nonblock+
161 * is +true+.
163 static VALUE take(int argc, VALUE *argv, VALUE self)
165 struct inread_args args;
166 VALUE buf;
167 VALUE tmp = rb_ivar_get(self, id_inotify_tmp);
168 struct inotify_event *e, *end;
169 ssize_t r;
170 VALUE rv = Qnil;
171 VALUE nonblock;
173 if (RARRAY_LEN(tmp) > 0)
174 return rb_ary_shift(tmp);
176 rb_scan_args(argc, argv, "01", &nonblock);
178 args.fd = rb_sp_fileno(self);
179 buf = rb_ivar_get(self, id_inotify_buf);
180 args.len = RSTRING_LEN(buf);
181 args.ptr = (struct inotify_event *)RSTRING_PTR(buf);
183 if (RTEST(nonblock))
184 rb_sp_set_nonblock(args.fd);
185 else
186 blocking_io_prepare(args.fd);
187 do {
188 r = rb_sp_io_region(inread, &args);
189 if (r == 0 /* Linux < 2.6.21 */
191 (r < 0 && errno == EINVAL) /* Linux >= 2.6.21 */
193 /* resize internal buffer */
194 int newlen;
195 if (args.len > 0x10000)
196 rb_raise(rb_eRuntimeError, "path too long");
197 if (ioctl(args.fd, FIONREAD, &newlen) != 0)
198 rb_sys_fail("ioctl(inotify,FIONREAD)");
199 rb_str_resize(buf, newlen);
200 args.ptr = (struct inotify_event *)RSTRING_PTR(buf);
201 args.len = newlen;
202 } else if (r < 0) {
203 if (errno == EAGAIN && RTEST(nonblock)) {
204 return Qnil;
205 } else {
206 if (!rb_io_wait_readable(args.fd))
207 rb_sys_fail("read(inotify)");
209 } else {
210 /* buffer in userspace to minimize read() calls */
211 end = (struct inotify_event *)((char *)args.ptr + r);
212 for (e = args.ptr; e < end; ) {
213 VALUE event = event_new(e);
214 if (NIL_P(rv))
215 rv = event;
216 else
217 rb_ary_push(tmp, event);
218 e = (struct inotify_event *)
219 ((char *)e + event_len(e));
222 } while (NIL_P(rv));
224 return rv;
228 * call-seq:
229 * inotify_event.events => [ :MOVED_TO, ... ]
231 * Returns an array of symbolic event names based on the contents of
232 * the +mask+ field.
234 static VALUE events(VALUE self)
236 long len = RARRAY_LEN(checks);
237 VALUE *ptr = RARRAY_PTR(checks);
238 VALUE pair;
239 VALUE sym;
240 VALUE rv = rb_ary_new();
241 uint32_t mask;
242 uint32_t event_mask = NUM2UINT(rb_funcall(self, id_mask, 0));
244 for (; (len -= 2) >= 0;) {
245 sym = *ptr++;
246 mask = NUM2UINT(*ptr++);
247 if ((event_mask & mask) == mask)
248 rb_ary_push(rv, sym);
251 return rv;
255 * call-seq:
256 * inotify.dup -> another Inotify object
258 * Duplicates an Inotify object, allowing it to be used in a blocking
259 * fashion in another thread. Ensures duplicated Inotify objects do
260 * not share read buffers, but do share the userspace Array buffer.
262 static VALUE init_copy(VALUE dest, VALUE orig)
264 VALUE tmp;
266 dest = rb_call_super(1, &orig); /* copy all other ivars as-is */
267 rb_ivar_set(dest, id_inotify_buf, rb_str_new(0, 128));
269 return dest;
273 * call-seq:
274 * ino.each { |event| ... } -> ino
276 * Yields each Inotify::Event received in a blocking fashion.
278 static VALUE each(VALUE self)
280 VALUE argv = Qfalse;
282 while (1)
283 rb_yield(take(0, &argv, self));
285 return self;
288 #if defined(NOGVL_CLOSE)
289 static VALUE fptr_close(void *ptr)
291 rb_io_t *fptr = ptr;
292 return (VALUE)close(fptr->fd);
296 * call-seq:
297 * ino.close -> nil
299 * Closes the underlying file descriptor and releases resources used by the
300 * kernel. Unlike other file descriptors, Inotify descriptors can take
301 * a long time to close(2). Calling this explicitly releases the GVL under
302 * Ruby 1.9
304 static VALUE nogvl_close(VALUE self)
306 rb_io_t *fptr;
308 GetOpenFile(self, fptr);
310 if ((int)rb_sp_io_region(fptr_close, fptr) < 0)
311 rb_sys_fail("close(inotify)");
312 fptr->fd = -1;
314 return Qnil;
316 #endif /* NOGVL_CLOSE */
318 void sleepy_penguin_init_inotify(void)
320 VALUE mSleepyPenguin, cInotify;
322 mSleepyPenguin = rb_define_module("SleepyPenguin");
325 * Document-class: SleepyPenguin::Inotify
327 * Inotify objects are used for monitoring file system events,
328 * it can monitor individual files or directories. When a directory
329 * is monitored it will return events for the directory itself and
330 * all files inside the directory.
332 * Inotify IO objects can be watched using IO.select or Epoll.
333 * IO#close may be called on the object when it is no longer needed.
335 * Inotify is available on Linux 2.6.13 or later.
337 * require "sleepy_penguin/sp"
338 * ino = SP::Inotify.new
339 * ino.add_watch("/path/to/foo", :OPEN)
340 * ino.each do |event|
341 * p event.events # => [ :OPEN ]
342 * end
344 cInotify = rb_define_class_under(mSleepyPenguin, "Inotify", rb_cIO);
345 rb_define_method(cInotify, "add_watch", add_watch, 2);
346 rb_define_method(cInotify, "rm_watch", rm_watch, 1);
347 rb_define_method(cInotify, "initialize_copy", init_copy, 1);
348 rb_define_method(cInotify, "take", take, -1);
349 rb_define_method(cInotify, "each", each, 0);
350 #ifdef NOGVL_CLOSE
351 rb_define_method(cInotify, "close", nogvl_close, 0);
352 #endif
355 * Document-class: SleepyPenguin::Inotify::Event
357 * Returned by SleepyPenguin::Inotify#take. It is a Struct with the
358 * following elements:
360 * - wd - watch descriptor (unsigned Integer)
361 * - mask - mask of events (unsigned Integer)
362 * - cookie - unique cookie associated related events (for rename)
363 * - name - optional string name (may be nil)
365 * The mask is a bitmask of the event flags accepted by
366 * Inotify#add_watch and may also include the following flags:
368 * - :IGNORED - watch was removed
369 * - :ISDIR - event occured on a directory
370 * - :Q_OVERFLOW - event queue overflowed (wd is -1)
371 * - :UNMOUNT - filesystem containing watched object was unmounted
373 * Use the Event#events method to get an array of symbols for the
374 * matched events.
376 cEvent = rb_struct_define("Event", "wd", "mask", "cookie", "name", 0);
377 cEvent = rb_define_class_under(cInotify, "Event", cEvent);
378 rb_define_method(cEvent, "events", events, 0);
379 rb_define_singleton_method(cInotify, "new", s_new, -1);
380 id_for_fd = rb_intern("for_fd");
381 id_inotify_buf = rb_intern("@inotify_buf");
382 id_inotify_tmp = rb_intern("@inotify_tmp");
383 id_mask = rb_intern("mask");
384 checks = rb_ary_new();
385 rb_global_variable(&checks);
386 #define IN(x) rb_define_const(cInotify,#x,UINT2NUM(IN_##x))
387 #define IN2(x) do { \
388 VALUE val = UINT2NUM(IN_##x); \
389 rb_define_const(cInotify,#x,val); \
390 rb_ary_push(checks, ID2SYM(rb_intern(#x))); \
391 rb_ary_push(checks, val); \
392 } while (0)
394 IN(ALL_EVENTS);
396 /* events a user can watch for */
397 IN2(ACCESS);
398 IN2(MODIFY);
399 IN2(ATTRIB);
400 IN2(CLOSE_WRITE);
401 IN2(CLOSE_NOWRITE);
402 IN2(OPEN);
403 IN2(MOVED_FROM);
404 IN2(MOVED_TO);
405 IN2(CREATE);
406 IN2(DELETE);
407 IN2(DELETE_SELF);
408 IN2(MOVE_SELF);
410 /* sent as needed to any watch */
411 IN2(UNMOUNT);
412 IN2(Q_OVERFLOW);
413 IN2(IGNORED);
414 IN2(ISDIR);
416 /* helpers */
417 IN(CLOSE);
418 IN(MOVE);
420 /* special flags */
421 IN(ONLYDIR);
422 IN(DONT_FOLLOW);
423 IN(EXCL_UNLINK);
424 IN(MASK_ADD);
425 IN(ONESHOT);
427 /* for inotify_init1() */
429 NODOC_CONST(cInotify, "NONBLOCK", INT2NUM(IN_NONBLOCK));
430 NODOC_CONST(cInotify, "CLOEXEC", INT2NUM(IN_CLOEXEC));
432 #endif /* HAVE_SYS_INOTIFY_H */