1 #ifdef HAVE_SYS_INOTIFY_H
2 #include "sleepy_penguin.h"
3 #include <sys/inotify.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)
12 static ID id_for_fd
, id_inotify_buf
, id_inotify_tmp
, id_mask
;
13 static VALUE cEvent
, checks
;
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
)
29 rb_scan_args(argc
, argv
, "01", &_flags
);
30 flags
= rb_sp_get_flags(klass
, _flags
);
32 fd
= inotify_init1(flags
);
34 if (errno
== EMFILE
|| errno
== ENFILE
|| errno
== ENOMEM
) {
36 fd
= inotify_init1(flags
);
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());
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
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
);
97 if (errno
== ENOMEM
) {
99 rc
= inotify_add_watch(fd
, pathname
, mask
);
102 rb_sys_fail("inotify_add_watch");
104 return UINT2NUM((uint32_t)rc
);
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
);
121 rb_sys_fail("inotify_rm_watch");
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
);
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
);
145 struct inotify_event
*ptr
;
149 static VALUE
inread(void *ptr
)
151 struct inread_args
*args
= ptr
;
153 return (VALUE
)read(args
->fd
, args
->ptr
, args
->len
);
158 * ino.take([nonblock]) -> Inotify::Event or nil
160 * Returns the next Inotify::Event processed. May return +nil+ if +nonblock+
163 static VALUE
take(int argc
, VALUE
*argv
, VALUE self
)
165 struct inread_args args
;
167 VALUE tmp
= rb_ivar_get(self
, id_inotify_tmp
);
168 struct inotify_event
*e
, *end
;
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
);
184 rb_sp_set_nonblock(args
.fd
);
186 blocking_io_prepare(args
.fd
);
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 */
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
);
203 if (errno
== EAGAIN
&& RTEST(nonblock
)) {
206 if (!rb_io_wait_readable(args
.fd
))
207 rb_sys_fail("read(inotify)");
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
);
217 rb_ary_push(tmp
, event
);
218 e
= (struct inotify_event
*)
219 ((char *)e
+ event_len(e
));
229 * inotify_event.events => [ :MOVED_TO, ... ]
231 * Returns an array of symbolic event names based on the contents of
234 static VALUE
events(VALUE self
)
236 long len
= RARRAY_LEN(checks
);
237 VALUE
*ptr
= RARRAY_PTR(checks
);
240 VALUE rv
= rb_ary_new();
242 uint32_t event_mask
= NUM2UINT(rb_funcall(self
, id_mask
, 0));
244 for (; (len
-= 2) >= 0;) {
246 mask
= NUM2UINT(*ptr
++);
247 if ((event_mask
& mask
) == mask
)
248 rb_ary_push(rv
, sym
);
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
)
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));
274 * ino.each { |event| ... } -> ino
276 * Yields each Inotify::Event received in a blocking fashion.
278 static VALUE
each(VALUE self
)
283 rb_yield(take(0, &argv
, self
));
288 #if defined(NOGVL_CLOSE)
289 static VALUE
fptr_close(void *ptr
)
292 return (VALUE
)close(fptr
->fd
);
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
304 static VALUE
nogvl_close(VALUE self
)
308 GetOpenFile(self
, fptr
);
310 if ((int)rb_sp_io_region(fptr_close
, fptr
) < 0)
311 rb_sys_fail("close(inotify)");
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 ]
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);
351 rb_define_method(cInotify
, "close", nogvl_close
, 0);
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
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); \
396 /* events a user can watch for */
410 /* sent as needed to any watch */
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 */