yakischloba-ktools 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,49 @@
1
+ Bringing common kernel APIs into Ruby using FFI.
2
+ http://github.com/yakischloba/ktools
3
+
4
+ Synopsis:
5
+
6
+ # irb(main):001:0> require 'ktools'
7
+ # => true
8
+ # irb(main):002:0> r, w = IO.pipe
9
+ # => [#<IO:0x4fa90c>, #<IO:0x4fa880>]
10
+ # irb(main):003:0> kq = Kqueue.new
11
+ # => #<Kernel::Kqueue:0x4f43a4 @kqfd=6, @fds={}>
12
+ # irb(main):004:0> kq.add(:socket, r, :events => [:read, :write])
13
+ # => true
14
+ # irb(main):005:0> kq.poll
15
+ # => []
16
+ # irb(main):006:0> w.write "foo"
17
+ # => 3
18
+ # irb(main):007:0> kq.poll
19
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
20
+ # irb(main):008:0> [r, w, kq].each {|i| i.close}
21
+
22
+
23
+ I plan to support the following kernel APIs:
24
+
25
+ kqueue (works - see tests/test_kqueue.rb)
26
+ epoll (works - see tests/test_epoll.rb. Needs Ruby wrapper.)
27
+ inotify
28
+ netlink
29
+
30
+ and maybe some others! I will at first hook up the C interfaces as directly
31
+ as possible, and then write Rubyist-friendly wrappers around them. Kqueue
32
+ currently has a (I think) decent wrapper and epoll is up next.
33
+
34
+
35
+ To install:
36
+
37
+ git clone git://github.com/yakischloba/ktools.git
38
+ cd ktools
39
+ gem build
40
+ sudo gem install ktools-<version>.gem
41
+
42
+ Also gems are on Rubyforge, so you can simply 'sudo gem install ktools', but commits will
43
+ be frequent for some time, so you'll probably want to be pulling the latest from Github.
44
+
45
+ Please file all issues on the Github issue tracker. Patches (with tests) are welcome and
46
+ encouraged, as are suggestions about API design, etc. It's all up in the air right now.
47
+
48
+ yakischloba on freenode
49
+ jakecdouglas at gmail
data/Rakefile ADDED
@@ -0,0 +1,77 @@
1
+ require 'rake' unless defined?(Rake)
2
+ require 'mkmf'
3
+ include Config
4
+
5
+ task :default => [:build, :test]
6
+ task :build => [:clean, :config, :objs, :shared]
7
+
8
+ def add_define(name)
9
+ $ktools_defines.push("-D#{name}")
10
+ end
11
+
12
+ task :config do
13
+ $ktools_defines = []
14
+ $ktools_dlext = RbConfig::expand(CONFIG['DLEXT'])
15
+ (add_define "HAVE_TBR" and build_against_ruby_stuff = true) if have_func('rb_thread_blocking_region')
16
+ add_define "HAVE_KQUEUE" if have_header("sys/event.h") and have_header("sys/queue.h")
17
+ #add_define "HAVE_INOTIFY" if inotify = have_func('inotify_init', 'sys/inotify.h')
18
+ #add_define "HAVE_OLD_INOTIFY" if !inotify && have_macro('__NR_inotify_init', 'sys/syscall.h')
19
+
20
+ if have_header('sys/epoll.h')
21
+ File.open("hasEpollTest.c", "w") {|f|
22
+ f.puts "#include <sys/epoll.h>"
23
+ f.puts "int main() { epoll_create(1024); return 0;}"
24
+ }
25
+ (e = system( "gcc hasEpollTest.c -o hasEpollTest " )) and (e = $?.to_i)
26
+ `rm -f hasEpollTest.c hasEpollTest`
27
+ add_define 'HAVE_EPOLL' if e == 0
28
+ end
29
+
30
+ $ktools_cc = `which #{RbConfig::expand(CONFIG["CC"])}`.chomp
31
+ $ktools_cflags = RbConfig::expand(CONFIG['CFLAGS']).split(" ")
32
+ $ktools_cflags.delete("$(cflags)")
33
+ $ktools_cflags = $ktools_cflags.join(" ")
34
+ $ktools_srcs = ["ktools.c"]
35
+ $ktools_srcs << "kqueue.c" if $ktools_defines.include?("-DHAVE_KQUEUE")
36
+ $ktools_srcs << "inotify.c" if $ktools_defines.include?("-DHAVE_INOTIFY")
37
+ $ktools_srcs << "epoll.c" if $ktools_defines.include?("-DHAVE_EPOLL")
38
+ $ktools_srcs << "netlink.c" if $ktools_defines.include?("-DHAVE_NETLINK")
39
+
40
+ if CONFIG["rubyhdrdir"]
41
+ hdrdir = RbConfig::expand(CONFIG["rubyhdrdir"])
42
+ $ktools_includes = "-I. -I#{hdrdir}/#{RbConfig::expand(CONFIG["sitearch"])} -I#{hdrdir}" if build_against_ruby_stuff
43
+ end
44
+
45
+ $ktools_ldshared = RbConfig::expand(CONFIG['LDSHARED'])
46
+ $ktools_ldshared << " -o ../lib/ktools.#{$ktools_dlext} " + $ktools_srcs.collect{|x| x.gsub(/\.c/, ".o")}.join(" ")
47
+ $ktools_ldshared << " -L#{RbConfig::expand(CONFIG['libdir'])} #{RbConfig::expand(CONFIG['LIBRUBYARG_SHARED'])}" if build_against_ruby_stuff
48
+ end
49
+
50
+ task :clean do
51
+ chdir "ext" do
52
+ sh "rm -f *.o *.bundle *.so"
53
+ end
54
+ chdir "lib" do
55
+ sh "rm -f *.o *.bundle *.so"
56
+ end
57
+ end
58
+
59
+ task :test do
60
+ require 'lib/ktools'
61
+ require 'bacon'
62
+ Bacon.summary_on_exit
63
+ load "tests/test_kqueue.rb" if Kernel.have_kqueue?
64
+ load "tests/test_epoll.rb" if Kernel.have_epoll?
65
+ end
66
+
67
+ task :objs do
68
+ chdir "ext" do
69
+ $ktools_srcs.each {|c| sh "#{$ktools_cc} #{$ktools_cflags} #{$ktools_defines.join(' ')} #{$ktools_includes} -c #{c}"}
70
+ end
71
+ end
72
+
73
+ task :shared do
74
+ chdir "ext" do
75
+ sh "#{$ktools_ldshared}"
76
+ end
77
+ end
data/ext/epoll.c ADDED
@@ -0,0 +1,32 @@
1
+ #ifdef HAVE_EPOLL
2
+
3
+ #include "epoll.h"
4
+
5
+ #ifdef HAVE_TBR
6
+ #include <ruby.h>
7
+ #endif
8
+
9
+ int wrap_epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout)
10
+ {
11
+ #ifdef HAVE_TBR
12
+ struct wrapped_epoll_event epevent;
13
+ epevent.epfd = epfd;
14
+ epevent.event = event;
15
+ epevent.maxevents = maxevents;
16
+ epevent.timeout = timeout;
17
+ epevent.result = -1;
18
+ rb_thread_blocking_region((rb_blocking_function_t *) tbr_epoll_wait, &epevent, RUBY_UBF_IO, 0);
19
+ return epevent.result;
20
+ #else
21
+ return epoll_wait(epfd, event, maxevents, timeout);
22
+ #endif
23
+ }
24
+
25
+ #ifdef HAVE_TBR
26
+ void tbr_epoll_wait(struct wrapped_epoll_event *event)
27
+ {
28
+ event->result = epoll_wait(event->epfd, event->event, event->maxevents, event->timeout);
29
+ }
30
+ #endif
31
+
32
+ #endif
data/ext/epoll.h ADDED
@@ -0,0 +1,17 @@
1
+ #ifdef HAVE_EPOLL
2
+
3
+ #include <sys/epoll.h>
4
+
5
+ #ifdef HAVE_TBR
6
+ struct wrapped_epoll_event {
7
+ int epfd;
8
+ struct epoll_event *event;
9
+ int maxevents;
10
+ int timeout;
11
+ int result;
12
+ };
13
+
14
+ void tbr_epoll_wait(struct wrapped_epoll_event*);
15
+ #endif
16
+
17
+ #endif
data/ext/kqueue.c ADDED
@@ -0,0 +1,39 @@
1
+ #ifdef HAVE_KQUEUE
2
+
3
+ #include "kqueue.h"
4
+
5
+ #ifdef HAVE_TBR
6
+ #include <ruby.h>
7
+ #endif
8
+
9
+ void wrap_evset(struct kevent *kev, unsigned int ident, short filter, unsigned short flags, unsigned int fflags, int data, void *udata)
10
+ {
11
+ EV_SET(kev, ident, filter, flags, fflags, data, udata);
12
+ }
13
+
14
+ int wrap_kevent(int kqfd, struct kevent *changelist, int nchanges, struct kevent *eventlist, int nevents, struct timespec *timeout)
15
+ {
16
+ #ifdef HAVE_TBR
17
+ struct wrapped_kevent wevent;
18
+ wevent.kqfd = kqfd;
19
+ wevent.changelist = changelist;
20
+ wevent.nchanges = nchanges;
21
+ wevent.eventlist = eventlist;
22
+ wevent.nevents = nevents;
23
+ wevent.timeout = timeout;
24
+ wevent.result = -1;
25
+ rb_thread_blocking_region((rb_blocking_function_t *) tbr_kevent, &wevent, RUBY_UBF_IO, 0);
26
+ return wevent.result;
27
+ #else
28
+ return kevent(kqfd, changelist, nchanges, eventlist, nevents, timeout);
29
+ #endif
30
+ }
31
+
32
+ #ifdef HAVE_TBR
33
+ void tbr_kevent(struct wrapped_kevent *wevent)
34
+ {
35
+ wevent->result = kevent(wevent->kqfd, wevent->changelist, wevent->nchanges, wevent->eventlist, wevent->nevents, wevent->timeout);
36
+ }
37
+ #endif
38
+
39
+ #endif
data/ext/kqueue.h ADDED
@@ -0,0 +1,20 @@
1
+ #ifdef HAVE_KQUEUE
2
+
3
+ #include <sys/event.h>
4
+ #include <sys/queue.h>
5
+
6
+ #ifdef HAVE_TBR
7
+ struct wrapped_kevent {
8
+ int kqfd;
9
+ struct kevent *changelist;
10
+ int nchanges;
11
+ struct kevent *eventlist;
12
+ int nevents;
13
+ struct timespec *timeout;
14
+ int result;
15
+ };
16
+
17
+ void tbr_kevent(struct wrapped_kevent*);
18
+ #endif
19
+
20
+ #endif
data/ext/ktools.c ADDED
@@ -0,0 +1,43 @@
1
+ #include "ktools.h"
2
+ #include <errno.h>
3
+
4
+ int get_errno()
5
+ {
6
+ return errno;
7
+ }
8
+
9
+ int have_kqueue()
10
+ {
11
+ #ifdef HAVE_KQUEUE
12
+ return 1;
13
+ #else
14
+ return 0;
15
+ #endif
16
+ }
17
+
18
+ int have_epoll()
19
+ {
20
+ #ifdef HAVE_EPOLL
21
+ return 1;
22
+ #else
23
+ return 0;
24
+ #endif
25
+ }
26
+
27
+ int have_inotify()
28
+ {
29
+ #ifdef HAVE_INOTIFY
30
+ return 1;
31
+ #else
32
+ return 0;
33
+ #endif
34
+ }
35
+
36
+ int have_netlink()
37
+ {
38
+ #ifdef HAVE_NETLINK
39
+ return 1;
40
+ #else
41
+ return 0;
42
+ #endif
43
+ }
data/ext/ktools.h ADDED
@@ -0,0 +1,20 @@
1
+ #ifndef KTOOLS_H
2
+ #define KTOOLS_H
3
+
4
+ #ifdef HAVE_EPOLL
5
+ #include "epoll.h"
6
+ #endif
7
+
8
+ #ifdef HAVE_KQUEUE
9
+ #include "kqueue.h"
10
+ #endif
11
+
12
+ /*#ifdef HAVE_NETLINK
13
+ #include "netlink.h"
14
+ #endif
15
+
16
+ #ifdef HAVE_INOTIFY
17
+ #include "inotify.h"
18
+ #endif*/
19
+
20
+ #endif
data/ktools.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "ktools"
3
+ s.version = "0.0.2"
4
+ s.date = "2009-04-22"
5
+ s.authors = ["Jake Douglas"]
6
+ s.email = "jakecdouglas@gmail.com"
7
+ s.rubyforge_project = "ktools"
8
+ s.has_rdoc = true
9
+ s.add_dependency('ffi')
10
+ s.add_dependency('bacon')
11
+ s.summary = "Bringing common kernel APIs into Ruby using FFI"
12
+ s.homepage = "http://www.github.com/yakischloba/ktools"
13
+ s.description = "Bringing common kernel APIs into Ruby using FFI"
14
+ s.extensions = ["Rakefile"]
15
+ s.files =
16
+ ["ktools.gemspec",
17
+ "README",
18
+ "Rakefile",
19
+ "lib/ktools.rb",
20
+ "lib/ktools/ktools.rb",
21
+ "lib/ktools/epoll.rb",
22
+ "lib/ktools/kqueue.rb",
23
+ "ext/ktools.c",
24
+ "ext/ktools.h",
25
+ "ext/epoll.c",
26
+ "ext/epoll.h",
27
+ "ext/kqueue.c",
28
+ "ext/kqueue.h",
29
+ "tests/test_epoll.rb",
30
+ "tests/test_kqueue.rb"]
31
+ end
@@ -0,0 +1,203 @@
1
+ module Kernel
2
+ class Epoll
3
+ extend FFI::Library
4
+
5
+ class Epoll_data < FFI::Struct
6
+ layout :ptr, :pointer,
7
+ :fd, :int,
8
+ :u32, :uint32,
9
+ :u64, :uint64
10
+ end
11
+
12
+ class Epoll_event < FFI::Struct
13
+ layout :events, :uint32,
14
+ :data, :pointer
15
+
16
+ def [] key
17
+ key == :data ? Epoll_data.new(super(key)) : super(key)
18
+ end
19
+ end
20
+
21
+ epc = FFI::ConstGenerator.new do |c|
22
+ c.include('sys/epoll.h')
23
+ c.const("EPOLLIN")
24
+ c.const("EPOLLPRI")
25
+ c.const("EPOLLOUT")
26
+ c.const("EPOLLRDNORM")
27
+ c.const("EPOLLRDBAND")
28
+ c.const("EPOLLWRNORM")
29
+ c.const("EPOLLWRBAND")
30
+ c.const("EPOLLMSG")
31
+ c.const("EPOLLERR")
32
+ c.const("EPOLLHUP")
33
+ c.const("EPOLLRDHUP")
34
+ c.const("EPOLLONESHOT")
35
+ c.const("EPOLLET")
36
+ c.const("EPOLL_CTL_ADD")
37
+ c.const("EPOLL_CTL_DEL")
38
+ c.const("EPOLL_CTL_MOD")
39
+ end
40
+
41
+ eval epc.to_ruby
42
+
43
+ EP_FLAGS = {
44
+ :read => EPOLLIN,
45
+ :write => EPOLLOUT,
46
+ :hangup => EPOLLHUP,
47
+ :priority => EPOLLPRI,
48
+ :edge => EPOLLET,
49
+ :oneshot => EPOLLONESHOT,
50
+ :error => EPOLLERR
51
+ }
52
+
53
+ EP_FLAGS[:remote_hangup] = EPOLLRDHUP if const_defined?("EPOLLRDHUP")
54
+
55
+ # Attach directly to epoll_create
56
+ attach_function :epoll_create, [:int], :int
57
+ # Attach directly to epoll_ctl
58
+ attach_function :epoll_ctl, [:int, :int, :int, :pointer], :int
59
+ # Attach to the epoll_wait wrapper so we can use rb_thread_blocking_region when possible
60
+ attach_function :epoll_wait, :wrap_epoll_wait, [:int, :pointer, :int, :int], :int
61
+
62
+ # Creates a new epoll event queue. Takes an optional size parameter (default 1024) that is a hint
63
+ # to the kernel about how many descriptors it will be handling. Read man epoll_create for details
64
+ # on this. Raises an error if the operation fails.
65
+ def initialize(size=1024)
66
+ @fds = {}
67
+ @epfd = epoll_create(size)
68
+ raise SystemCallError.new("Error creating epoll descriptor", get_errno) unless @epfd > 0
69
+ end
70
+
71
+ # Generic method for adding events. This simply calls the proper add_foo method specified by the type symbol.
72
+ # Example:
73
+ # ep.add(:socket, sock, :events => [:read])
74
+ # calls -> ep.add_socket(sock, events => [:read])
75
+ #
76
+ # Note: even though epoll only supports :socket style descriptors, we keep this for consistency with other APIs.
77
+ def add(type, target, options={})
78
+ case type
79
+ when :socket
80
+ add_socket(target, options)
81
+ else
82
+ raise ArgumentError.new("Epoll only supports socket style descriptors")
83
+ end
84
+ end
85
+
86
+ # Add events to a socket-style descriptor (socket or pipe). Your target can be either
87
+ # an IO object (socket, pipe), or a file descriptor number.
88
+ #
89
+ # Supported :events are:
90
+ #
91
+ # * :read - The descriptor has become readable.
92
+ # * :write - The descriptor has become writeable.
93
+ # * :priority - There is urgent data available for read operations.
94
+ # * :error - Error condition happened on the associated file descriptor. (Always active)
95
+ # * :hangup - Hang up happened on the associated file descriptor. (Always active)
96
+ # * :remote_hangup - Stream socket peer closed the connection, or shut down writing half of connection. (Missing from some kernel verions)
97
+ #
98
+ # Supported :flags are:
99
+ #
100
+ # * :edge - Sets the Edge Triggered behavior for the associated file descriptor. (see manpage)
101
+ # * :oneshot - Sets the one-shot behaviour for the associated file descriptor. (Event only fires once)
102
+ #
103
+ # Example:
104
+ #
105
+ # irb(main):001:0> require 'ktools'
106
+ # => true
107
+ # irb(main):002:0> r, w = IO.pipe
108
+ # => [#<IO:0x89be38c>, #<IO:0x89be378>]
109
+ # irb(main):003:0> ep = Epoll.new
110
+ # => #<Kernel::Epoll:0x89bca3c @fds={}, @epfd=5>
111
+ # irb(main):004:0> ep.add(:socket, r, :events => [:read])
112
+ # => true
113
+ # irb(main):005:0> ep.poll
114
+ # => []
115
+ # irb(main):006:0> w.write 'foo'
116
+ # => 3
117
+ # irb(main):007:0> ep.poll
118
+ # => [{:target=>#<IO:0x89be38c>, :event=>:read, :type=>:socket}]
119
+ # irb(main):008:0> [r, w, ep].each{|x| x.close }
120
+ def add_socket(target, options={})
121
+ fdnum = target.respond_to?(:fileno) ? target.fileno : target
122
+ events = (options[:events] + (options[:flags] || [])).inject(0){|m,i| m | EP_FLAGS[i]}
123
+
124
+ ev = Epoll_event.new
125
+ ev[:events] = events
126
+ ev[:data] = Epoll_data.new
127
+ ev[:data][:fd] = fdnum
128
+
129
+ if epoll_ctl(@epfd, EPOLL_CTL_ADD, fdnum, ev) == -1
130
+ return false
131
+ else
132
+ @fds[fdnum] = {:target => target, :event => ev}
133
+ return true
134
+ end
135
+ end
136
+
137
+ # Poll for an event. Pass an optional timeout float as number of seconds to wait for an event. Default is 0.0 (do not wait).
138
+ #
139
+ # Using a timeout will block for the duration of the timeout. Under Ruby 1.9.1, we use rb_thread_blocking_region() under the
140
+ # hood to allow other threads to run during this call. Prior to 1.9 though, we do not have native threads and hence this call
141
+ # will block the whole interpreter (all threads) until it returns.
142
+ #
143
+ # This call returns an array of hashes, similar to the following:
144
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
145
+ #
146
+ # * :type - will be the type of event target, i.e. an event set with #add_socket will have :type => :socket
147
+ # * :target - the 'target' or 'subject' of the event. This can be a File, IO, process or signal number.
148
+ # * :event - the event that occurred on the target. This is one of the symbols you passed as :events => [:foo] when adding the event.
149
+ #
150
+ # Note: even though epoll only supports :socket style descriptors, we keep :type for consistency with other APIs.
151
+ def poll(timeout=0.0)
152
+ timeout = (timeout * 1000).to_i
153
+ ev = Epoll_event.new
154
+ case epoll_wait(@epfd, ev, 1, timeout)
155
+ when -1
156
+ [errno]
157
+ when 0
158
+ []
159
+ else
160
+ [process_event(ev)]
161
+ end
162
+ end
163
+
164
+ def process_event(ev) #:nodoc:
165
+ h = @fds[ev[:data][:fd]]
166
+ return nil if h.nil?
167
+
168
+ event = if ev[:events] & EPOLLIN == EPOLLIN
169
+ :read
170
+ elsif ev[:events] & EPOLLOUT == EPOLLOUT
171
+ :write
172
+ elsif ev[:events] & ERPOLLPRI == EPOLLPRI
173
+ :priority
174
+ elsif ev[:events] & EPOLLERR == EPOLLERR
175
+ :error
176
+ elsif ev[:events] & EPOLLHUP == EPOLLHUP
177
+ :hangup
178
+ elsif Epoll.const_defined?("EPOLLRDHUP") and ev[:events] & EPOLLRDHUP == EPOLLRDHUP
179
+ :remote_hangup
180
+ end
181
+
182
+ delete(:socket, h[:target]) if ev[:events] & EPOLLONESHOT == EPOLLONESHOT
183
+ {:target => h[:target], :event => event, :type => :socket}
184
+ end
185
+
186
+ # Stop generating events for the given type and event target, ie:
187
+ # ep.delete(:socket, sock)
188
+ #
189
+ # Note: even though epoll only supports :socket style descriptors, we keep this for consistency with other APIs.
190
+ def delete(type, target)
191
+ ident = target.respond_to?(:fileno) ? target.fileno : target
192
+ h = @fds[ident]
193
+ return false if h.nil?
194
+ epoll_ctl(@epfd, EPOLL_CTL_DEL, ident, h[:event])
195
+ return true
196
+ end
197
+
198
+ def close
199
+ IO.for_fd(@epfd).close
200
+ end
201
+
202
+ end
203
+ end
@@ -0,0 +1,388 @@
1
+ module Kernel
2
+
3
+ class Kqueue
4
+ extend FFI::Library
5
+
6
+ class Kevent < FFI::Struct
7
+ layout :ident, :uint,
8
+ :filter, :short,
9
+ :flags, :ushort,
10
+ :fflags, :uint,
11
+ :data, :int,
12
+ :udata, :pointer
13
+ end
14
+
15
+ class Timespec < FFI::Struct
16
+ layout :tv_sec, :long,
17
+ :tv_nsec, :long
18
+ end
19
+
20
+ kqc = FFI::ConstGenerator.new do |c|
21
+ c.include 'sys/event.h'
22
+
23
+ # filters - signed short
24
+ c.const("EVFILT_READ", "%d")
25
+ c.const("EVFILT_WRITE", "%d")
26
+ c.const("EVFILT_AIO", "%d")
27
+ c.const("EVFILT_VNODE", "%d")
28
+ c.const("EVFILT_PROC", "%d")
29
+ c.const("EVFILT_SIGNAL", "%d")
30
+
31
+ # flags - unsigned short
32
+ c.const("EV_ADD", "%u")
33
+ c.const("EV_DELETE", "%u")
34
+ c.const("EV_ENABLE", "%u")
35
+ c.const("EV_DISABLE", "%u")
36
+ c.const("EV_RECEIPT", "%u")
37
+ c.const("EV_ONESHOT", "%u")
38
+ c.const("EV_CLEAR", "%u")
39
+ c.const("EV_SYSFLAGS", "%u")
40
+ c.const("EV_FLAG0", "%u")
41
+ c.const("EV_FLAG1", "%u")
42
+ c.const("EV_EOF", "%u")
43
+ c.const("EV_ERROR", "%u")
44
+ c.const("EV_POLL", "%u")
45
+ c.const("EV_OOBAND", "%u")
46
+
47
+ # fflags - unsigned int
48
+ c.const("NOTE_LOWAT", "%u")
49
+ c.const("NOTE_DELETE", "%u")
50
+ c.const("NOTE_WRITE", "%u")
51
+ c.const("NOTE_EXTEND", "%u")
52
+ c.const("NOTE_ATTRIB", "%u")
53
+ c.const("NOTE_LINK", "%u")
54
+ c.const("NOTE_RENAME", "%u")
55
+ c.const("NOTE_REVOKE", "%u")
56
+ c.const("NOTE_EXIT", "%u")
57
+ c.const("NOTE_FORK", "%u")
58
+ c.const("NOTE_EXEC", "%u")
59
+ c.const("NOTE_REAP", "%u")
60
+ c.const("NOTE_SIGNAL", "%u")
61
+ c.const("NOTE_PDATAMASK", "%u")
62
+ c.const("NOTE_PCTRLMASK", "%u")
63
+ c.const("NOTE_SECONDS", "%u")
64
+ c.const("NOTE_USECONDS", "%u")
65
+ c.const("NOTE_NSECONDS", "%u")
66
+ c.const("NOTE_ABSOLUTE", "%u")
67
+ c.const("NOTE_TRACK", "%u")
68
+ c.const("NOTE_TRACKERR", "%u")
69
+ c.const("NOTE_CHILD", "%u")
70
+ end
71
+
72
+ eval kqc.to_ruby
73
+
74
+ KQ_FILTERS = {
75
+ :read => EVFILT_READ,
76
+ :write => EVFILT_WRITE,
77
+ :file => EVFILT_VNODE,
78
+ :process => EVFILT_PROC,
79
+ :signal => EVFILT_SIGNAL
80
+ }
81
+
82
+ KQ_FLAGS = {
83
+ :add => EV_ADD,
84
+ :enable => EV_ENABLE,
85
+ :disable => EV_DISABLE,
86
+ :delete => EV_DELETE,
87
+ :oneshot => EV_ONESHOT,
88
+ :clear => EV_CLEAR
89
+ }
90
+
91
+ KQ_FFLAGS = {
92
+ :delete => NOTE_DELETE,
93
+ :write => NOTE_WRITE,
94
+ :extend => NOTE_EXTEND,
95
+ :attrib => NOTE_ATTRIB,
96
+ :link => NOTE_LINK,
97
+ :rename => NOTE_RENAME,
98
+ :revoke => NOTE_REVOKE,
99
+ :exit => NOTE_EXIT,
100
+ :fork => NOTE_FORK,
101
+ :exec => NOTE_EXEC
102
+ }
103
+
104
+ # Leopard has these, Tiger does not.
105
+ KQ_FLAGS[:receipt] = EV_RECEIPT if const_defined?("EV_RECEIPT")
106
+ KQ_FFLAGS[:signal] = NOTE_SIGNAL if const_defined?("NOTE_SIGNAL")
107
+ KQ_FFLAGS[:reap] = NOTE_REAP if const_defined?("NOTE_REAP")
108
+
109
+ # Had to write a wrapper for EV_SET since its a macro
110
+ attach_function :ev_set, :wrap_evset, [:pointer, :uint, :short, :ushort, :uint, :int, :pointer], :void
111
+ # Attach directly to kqueue function, no wrapper needed
112
+ attach_function :kqueue, [], :int
113
+ # We wrap kqueue and kevent because we use rb_thread_blocking_region when it's available in MRI
114
+ attach_function :kevent, :wrap_kevent, [:int, :pointer, :int, :pointer, :int, Timespec], :int
115
+
116
+ # We provide the raw C interface above. Now we OO-ify it.
117
+
118
+ # Creates a new kqueue event queue. Will raise an error if the operation fails.
119
+ def initialize
120
+ @fds = {}
121
+ @pids = {}
122
+ @kqfd = kqueue
123
+ raise SystemCallError.new("Error creating kqueue descriptor", get_errno) unless @kqfd > 0
124
+ end
125
+
126
+ # Generic method for adding events. This simply calls the proper add_foo method specified by the type symbol.
127
+ # Example:
128
+ # kq.add(:process, pid, :events => [:fork])
129
+ # calls -> kq.add_process(pid, events => [:fork])
130
+ def add(type, target, options={})
131
+ case type
132
+ when :socket
133
+ add_socket(target, options)
134
+ when :file
135
+ add_file(target, options)
136
+ when :process
137
+ add_process(target, options)
138
+ when :signal
139
+ add_signal(target, options)
140
+ else
141
+ raise ArgumentError.new("Unknown event type #{type}")
142
+ end
143
+ end
144
+
145
+ # Add events on a file to the Kqueue. kqueue requires that a file actually be opened (to get a descriptor)
146
+ # before it can be monitored. You can pass a File object here, or a String of the pathname, in which
147
+ # case we'll try to open the file for you. In either case, a File object will be returned as the :target
148
+ # in the event returned by #poll. If you want to keep track of it yourself, you can just pass the file
149
+ # descriptor number (and that's what you'll get back.)
150
+ #
151
+ # Valid events here are as follows, using descriptions from the kqueue man pages:
152
+ #
153
+ # * :delete - "The unlink() system call was called on the file referenced by the descriptor."
154
+ # * :write - "A write occurred on the file referenced by the descriptor."
155
+ # * :extend - "The file referenced by the descriptor was extended."
156
+ # * :attrib - "The file referenced by the descriptor had its attributes changed."
157
+ # * :link - "The link count on the file changed."
158
+ # * :rename - "The file referenced by the descriptor was renamed."
159
+ # * :revoke - "Access to the file was revoked via revoke(2) or the underlying fileystem was unmounted."
160
+ #
161
+ # Example:
162
+ # irb(main):001:0> require 'ktools'
163
+ # => true
164
+ # irb(main):002:0> file = Tempfile.new("kqueue-test")
165
+ # => #<File:/tmp/kqueue-test20090417-602-evm5wc-0>
166
+ # irb(main):003:0> kq = Kqueue.new
167
+ # => #<Kernel::Kqueue:0x4f0aec @kqfd=5, @fds={}>
168
+ # irb(main):004:0> kq.add(:file, file, :events => [:write, :delete])
169
+ # => true
170
+ # irb(main):005:0> kq.poll
171
+ # => []
172
+ # irb(main):006:0> file.delete
173
+ # => #<File:/tmp/kqueue-test20090417-602-evm5wc-0>
174
+ # irb(main):007:0> kq.poll
175
+ # => [{:type=>:file, :target=>#<File:/tmp/kqueue-test20090417-602-evm5wc-0>, :event=>:delete}]
176
+ # irb(main):008:0> file.close and kq.close
177
+ # => nil
178
+ def add_file(file, options={})
179
+ fflags, flags = options.values_at :events, :flags
180
+ raise ArgumentError.new("must specify which file events to watch for") unless fflags
181
+
182
+ file = file.kind_of?(String) ? File.open(file, 'r') : file
183
+ fdnum = file.respond_to?(:fileno) ? file.fileno : file
184
+
185
+ k = Kevent.new
186
+ flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
187
+ fflags = fflags.inject(0){|m,i| m | KQ_FFLAGS[i] }
188
+ ev_set(k, fdnum, EVFILT_VNODE, EV_ADD | flags, fflags, 0, nil)
189
+
190
+ if kevent(@kqfd, k, 1, nil, 0, nil) == -1
191
+ return false
192
+ else
193
+ @fds[fdnum] = {:target => file, :event => k}
194
+ return true
195
+ end
196
+ end
197
+
198
+ # Add events to a socket-style descriptor (socket or pipe). Your target can be either
199
+ # an IO object (socket, pipe), or a file descriptor number.
200
+ #
201
+ # Supported events are:
202
+ #
203
+ # * :read - The descriptor has become readable.
204
+ # * :write - The descriptor has become writeable.
205
+ #
206
+ # See the kqueue manpage for how behavior differs depending on the descriptor types.
207
+ # In general, you shouldn't have to worry about it.
208
+ #
209
+ # Example:
210
+ # irb(main):001:0> require 'ktools'
211
+ # => true
212
+ # irb(main):002:0> r, w = IO.pipe
213
+ # => [#<IO:0x4fa90c>, #<IO:0x4fa880>]
214
+ # irb(main):003:0> kq = Kqueue.new
215
+ # => #<Kernel::Kqueue:0x4f43a4 @kqfd=6, @fds={}>
216
+ # irb(main):004:0> kq.add(:socket, r, :events => [:read, :write])
217
+ # => true
218
+ # irb(main):005:0> kq.poll
219
+ # => []
220
+ # irb(main):006:0> w.write "foo"
221
+ # => 3
222
+ # irb(main):007:0> kq.poll
223
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
224
+ # irb(main):008:0> [r, w, kq].each {|i| i.close}
225
+ def add_socket(target, options={})
226
+ filters, flags = options.values_at :events, :flags
227
+ flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
228
+ filters = filters ? filters.inject(0){|m,i| m | KQ_FILTERS[i] } : EVFILT_READ | EVFILT_WRITE
229
+ fdnum = target.respond_to?(:fileno) ? target.fileno : target
230
+
231
+ k = Kevent.new
232
+ ev_set(k, fdnum, filters, EV_ADD | flags, 0, 0, nil)
233
+
234
+ if kevent(@kqfd, k, 1, nil, 0, nil) == -1
235
+ return false
236
+ else
237
+ @fds[fdnum] = {:target => target, :event => k}
238
+ return true
239
+ end
240
+ end
241
+
242
+ # Add events for a process. Takes a process id and and options hash. Supported events are:
243
+ # * :exit - The process has exited
244
+ # * :fork - The process has created a child process via fork(2) or similar call.
245
+ # * :exec - The process executed a new process via execve(2) or similar call.
246
+ # * :signal - The process was sent a signal. Status can be checked via waitpid(2) or similar call.
247
+ # * :reap - The process was reaped by the parent via wait(2) or similar call.\
248
+ #
249
+ # Note: SIGNAL and REAP do not appear to exist in OSX older than Leopard.
250
+ #
251
+ # Example:
252
+ # irb(main):001:0> require 'ktools'
253
+ # => true
254
+ # irb(main):002:0> kq = Kqueue.new
255
+ # => #<Kernel::Kqueue:0x14f55b4 @kqfd=4, @pids={}, @fds={}>
256
+ # irb(main):003:0> fpid = fork{ sleep }
257
+ # => 616
258
+ # irb(main):004:0> kq.add(:process, fpid, :events => [:exit])
259
+ # => true
260
+ # irb(main):005:0> Process.kill('TERM', fpid)
261
+ # => 1
262
+ # irb(main):006:0> kq.poll.first
263
+ # => {:event=>:exit, :type=>:process, :target=>616}
264
+ #
265
+ def add_process(pid, options={})
266
+ flags, fflags = options.values_at :flags, :events
267
+ flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
268
+ fflags = fflags.inject(0){|m,i| m | KQ_FFLAGS[i] }
269
+
270
+ k = Kevent.new
271
+ ev_set(k, pid, EVFILT_PROC, EV_ADD | flags, fflags, 0, nil)
272
+
273
+ if kevent(@kqfd, k, 1, nil, 0, nil) == -1
274
+ return false
275
+ else
276
+ @pids[pid] = {:target => pid, :event => k}
277
+ return true
278
+ end
279
+ end
280
+
281
+ # Poll for an event. Pass an optional timeout float as number of seconds to wait for an event. Default is 0.0 (do not wait).
282
+ #
283
+ # Using a timeout will block for the duration of the timeout. Under Ruby 1.9.1, we use rb_thread_blocking_region() under the
284
+ # hood to allow other threads to run during this call. Prior to 1.9 though, we do not have native threads and hence this call
285
+ # will block the whole interpreter (all threads) until it returns.
286
+ #
287
+ # This call returns an array of hashes, similar to the following:
288
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
289
+ #
290
+ # * :type - will be the type of event target, i.e. an event set with #add_file will have :type => :file
291
+ # * :target - the 'target' or 'subject' of the event. This can be a File, IO, process or signal number.
292
+ # * :event - the event that occurred on the target. This is one of the symbols you passed as :events => [:foo] when adding the event.
293
+ def poll(timeout=0.0)
294
+ k = Kevent.new
295
+ t = Timespec.new
296
+ t[:tv_sec] = timeout.to_i
297
+ t[:tv_nsec] = ((timeout - timeout.to_i) * 1e9).to_i
298
+
299
+ case kevent(@kqfd, nil, 0, k, 1, t)
300
+ when -1
301
+ [errno]
302
+ when 0
303
+ []
304
+ else
305
+ [process_event(k)]
306
+ end
307
+ end
308
+
309
+ def process_event(k) #:nodoc:
310
+ res = case k[:filter]
311
+ when EVFILT_VNODE
312
+ h = @fds[k[:ident]]
313
+ return nil if h.nil?
314
+ event = if k[:fflags] & NOTE_DELETE == NOTE_DELETE
315
+ :delete
316
+ elsif k[:fflags] & NOTE_WRITE == NOTE_WRITE
317
+ :write
318
+ elsif k[:fflags] & NOTE_EXTEND == NOTE_EXTEND
319
+ :extend
320
+ elsif k[:fflags] & NOTE_ATTRIB == NOTE_ATTRIB
321
+ :attrib
322
+ elsif k[:fflags] & NOTE_LINK == NOTE_LINK
323
+ :link
324
+ elsif k[:fflags] & NOTE_RENAME == NOTE_RENAME
325
+ :rename
326
+ elsif k[:fflags] & NOTE_REVOKE == NOTE_REVOKE
327
+ :revoke
328
+ end
329
+ delete(:file, k[:ident]) if event == :delete || event == :revoke
330
+ {:target => h[:target], :type => :file, :event => event}
331
+ when EVFILT_READ
332
+ h = @fds[k[:ident]]
333
+ return nil if h.nil?
334
+ {:target => h[:target], :type => :socket, :event => :read}
335
+ when EVFILT_WRITE
336
+ h = @fds[k[:ident]]
337
+ return nil if h.nil?
338
+ {:target => h[:target], :type => :socket, :event => :write}
339
+ when EVFILT_PROC
340
+ h = @pids[k[:ident]]
341
+ return nil if h.nil?
342
+ event = if k[:fflags] & NOTE_EXIT == NOTE_EXIT
343
+ :exit
344
+ elsif k[:fflags] & NOTE_FORK == NOTE_FORK
345
+ :fork
346
+ elsif k[:fflags] & NOTE_EXEC == NOTE_EXEC
347
+ :exec
348
+ elsif Kqueue.const_defined?("NOTE_SIGNAL") and k[:fflags] & NOTE_SIGNAL == NOTE_SIGNAL
349
+ :signal
350
+ elsif Kqueue.const_defined?("NOTE_REAP") and k[:fflags] & NOTE_REAP == NOTE_REAP
351
+ :reap
352
+ end
353
+ delete(:process, k[:ident]) if event == :exit
354
+ {:target => h[:target], :type => :process, :event => event}
355
+ end
356
+
357
+ delete(res[:type], res[:target]) if k[:flags] & EV_ONESHOT == EV_ONESHOT
358
+ res
359
+ end
360
+
361
+ # Stop generating events for the given type and event target, ie:
362
+ # kq.delete(:process, 6244)
363
+ def delete(type, target)
364
+ ident = target.respond_to?(:fileno) ? target.fileno : target
365
+ container = case type
366
+ when :socket
367
+ @fds
368
+ when :file
369
+ @fds
370
+ when :process
371
+ @pids
372
+ end
373
+ h = container[ident]
374
+ return false if h.nil?
375
+ k = h[:event]
376
+ ev_set(k, k[:ident], k[:filter], EV_DELETE, k[:fflags], 0, nil)
377
+ kevent(@kqfd, k, 1, nil, 0, nil)
378
+ container.delete(ident)
379
+ return true
380
+ end
381
+
382
+ # Close the kqueue descriptor. This essentially shuts down your kqueue and renders all active events on this kqueue removed.
383
+ def close
384
+ IO.for_fd(@kqfd).close
385
+ end
386
+
387
+ end
388
+ end
@@ -0,0 +1,18 @@
1
+ module Kernel
2
+ extend FFI::Library
3
+ ffi_lib Dir.glob(File.dirname(File.expand_path(__FILE__)) + "/../ktools.*").select{|x| x =~ /\.so$/ || x =~ /\.bundle$/}.first
4
+
5
+ # Tells us from Ruby whether or not we have built with support for these libraries
6
+ %w[epoll kqueue inotify netlink].each do |m|
7
+ attach_function "have_#{m}".to_sym, [], :int
8
+ define_method("have_#{m}?") { (self.send "have_#{m}") > 0 ? true : false }
9
+ end
10
+
11
+ attach_function :get_errno, [], :int
12
+
13
+ # Returns the current system errno as a Ruby Errno object
14
+ def errno
15
+ SystemCallError.new(get_errno)
16
+ end
17
+
18
+ end
data/lib/ktools.rb ADDED
@@ -0,0 +1,6 @@
1
+ $:.unshift File.expand_path(File.dirname(File.expand_path(__FILE__)))
2
+ require 'ffi'
3
+ require 'ffi/tools/const_generator'
4
+ require 'ktools/ktools'
5
+ require 'ktools/kqueue' if Kernel.have_kqueue?
6
+ require 'ktools/epoll' if Kernel.have_epoll?
@@ -0,0 +1,69 @@
1
+ describe "the epoll interface" do
2
+
3
+ it "should return a valid epoll file descriptor" do
4
+ @epfd = Epoll::epoll_create(10)
5
+ @epfd.class.should.equal Fixnum
6
+ @epfd.should.be > 0
7
+ end
8
+
9
+ it "should set an event for readable status of a descriptor, and retrieve the event when it occurs" do
10
+ r, w = IO.pipe
11
+ ev = Epoll::Epoll_event.new
12
+ ev[:events] = Epoll::EPOLLIN
13
+ ev[:data] = Epoll::Epoll_data.new
14
+ ev[:data][:fd] = r.fileno
15
+ ev[:data][:u32] = 12345
16
+ Epoll::epoll_ctl(@epfd, Epoll::EPOLL_CTL_ADD, r.fileno, ev).should.equal 0
17
+ rev = Epoll::Epoll_event.new
18
+ Epoll::epoll_wait(@epfd, rev, 1, 50).should.equal 0
19
+ w.write "foo"
20
+ Epoll::epoll_wait(@epfd, rev, 1, 50).should.equal 1
21
+ rev[:events].should.equal Epoll::EPOLLIN
22
+ rev[:data][:fd].should.equal r.fileno
23
+ rev[:data][:u32].should.equal 12345
24
+ w.close
25
+ r.close
26
+ w.should.be.closed
27
+ r.should.be.closed
28
+ end
29
+
30
+ it "should close the epoll file descriptor" do
31
+ i = IO.for_fd(@epfd)
32
+ i.should.not.be.closed
33
+ i.close
34
+ i.should.be.closed
35
+ end
36
+
37
+ it "should add socket events and retrieve them using the Ruby API" do
38
+ r, w = IO.pipe
39
+ ep = Epoll.new
40
+
41
+ ep.add(:socket, r, :events => [:read]).should.be.true
42
+ ep.poll.should.be.empty
43
+
44
+ w.write 'foo'
45
+ res = ep.poll.first
46
+
47
+ res[:event].should.equal :read
48
+ res[:target].fileno.should.equal r.fileno
49
+ res[:type].should.equal :socket
50
+
51
+ [r,w,ep].each{|i| i.close}
52
+ end
53
+
54
+ it "should delete events using the Ruby API" do
55
+ r, w = IO.pipe
56
+ ep = Epoll.new
57
+
58
+ ep.add(:socket, r, :events => [:read]).should.be.true
59
+ ep.poll.should.be.empty
60
+
61
+ w.write 'foo'
62
+ ep.delete(:socket, r).should.be.true
63
+
64
+ ep.poll.should.be.empty
65
+
66
+ [r,w,ep].each{|i| i.close}
67
+ end
68
+
69
+ end
@@ -0,0 +1,109 @@
1
+ describe "the kqueue interface" do
2
+
3
+ it "should return a valid kqueue file descriptor" do
4
+ @kqfd = Kqueue::kqueue
5
+ @kqfd.class.should.equal Fixnum
6
+ @kqfd.should.be > 0
7
+ end
8
+
9
+ it "should use the raw C API to set an event for file watching, and retrieve the event when it occurs" do
10
+ file = Tempfile.new("kqueue-test")
11
+ k = Kqueue::Kevent.new
12
+ k.should.not.be.nil
13
+ Kqueue::ev_set(k, file.fileno, Kqueue::EVFILT_VNODE, Kqueue::EV_ADD | Kqueue::EV_CLEAR, Kqueue::NOTE_WRITE, 0, nil)
14
+ Kqueue::kevent(@kqfd, k, 1, nil, 0, nil)
15
+ File.open(file.path, 'w'){|x| x.puts 'foo'}
16
+ n = Kqueue::Kevent.new
17
+ res = Kqueue::kevent(@kqfd, nil, 0, n, 1, nil)
18
+ res.should.be > -1
19
+ n[:ident].should.equal file.fileno
20
+ n[:filter].should.equal Kqueue::EVFILT_VNODE
21
+ n[:fflags].should.equal Kqueue::NOTE_WRITE
22
+ file.close
23
+ end
24
+
25
+ it "should close the kqueue file descriptor" do
26
+ i = IO.for_fd(@kqfd)
27
+ i.should.not.be.closed
28
+ i.close
29
+ i.should.be.closed
30
+ end
31
+
32
+ it "should add file events using the ruby API" do
33
+ file = Tempfile.new("kqueue-test")
34
+ kq = Kqueue.new
35
+ kq.add(:file, file, :events => [:write, :delete]).should.be.true
36
+
37
+ kq.poll.should.be.empty
38
+ File.open(file.path, 'w'){|x| x.puts 'foo'}
39
+
40
+ res = kq.poll.first
41
+ res.class.should.equal Hash
42
+ res[:target].class.should.equal Tempfile
43
+ res[:target].fileno.should.equal file.fileno
44
+ res[:type].should.equal :file
45
+ res[:event].should.equal :write
46
+
47
+ kq.poll.should.be.empty
48
+ file.delete
49
+
50
+ res2 = kq.poll.first
51
+ res2[:target].class.should.equal Tempfile
52
+ res2[:target].fileno.should.equal file.fileno
53
+ res2[:type].should.equal :file
54
+ res2[:event].should.equal :delete
55
+
56
+ file.close
57
+ kq.close
58
+ end
59
+
60
+ it "should add events for socket-style descriptors, then delete them, using the Ruby API" do
61
+ r, w = IO.pipe
62
+ kq = Kqueue.new
63
+ kq.add(:socket, r, :events => [:read]).should.be.true
64
+
65
+ kq.poll.should.be.empty
66
+ w.write "foo"
67
+
68
+ res = kq.poll.first
69
+ res[:target].class.should.equal IO
70
+ res[:target].fileno.should.equal r.fileno
71
+ res[:type].should.equal :socket
72
+ res[:event].should.equal :read
73
+
74
+ kq.poll.should.be.empty
75
+ kq.delete(:socket, r).should.be.true
76
+ w.write "foo"
77
+ kq.poll.should.be.empty
78
+
79
+ [r,w,kq].each{|i| i.close}
80
+ end
81
+
82
+ it "should add events for a process using the Ruby API" do
83
+ kq = Kqueue.new
84
+ # Watch for ourself to fork
85
+ kq.add(:process, Process.pid, :events => [:fork]).should.be.true
86
+
87
+ fpid = fork{ at_exit {exit!}; sleep }
88
+
89
+ res = kq.poll(1).first
90
+ res[:target].should.equal Process.pid
91
+ res[:type].should.equal :process
92
+ res[:event].should.equal :fork
93
+
94
+ # Watch for the child to exit and kill it
95
+ kq.add(:process, fpid, :events => [:exit])
96
+ sleep 0.5
97
+ Process.kill('TERM', fpid)
98
+
99
+ res2 = kq.poll(1).first
100
+ res2[:target].should.equal fpid
101
+ res2[:type].should.equal :process
102
+ res2[:event].should.equal :exit
103
+
104
+ kq.poll.should.be.empty
105
+
106
+ kq.close
107
+ end
108
+
109
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yakischloba-ktools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Jake Douglas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-22 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ffi
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: bacon
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: Bringing common kernel APIs into Ruby using FFI
36
+ email: jakecdouglas@gmail.com
37
+ executables: []
38
+
39
+ extensions:
40
+ - Rakefile
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - ktools.gemspec
45
+ - README
46
+ - Rakefile
47
+ - lib/ktools.rb
48
+ - lib/ktools/ktools.rb
49
+ - lib/ktools/epoll.rb
50
+ - lib/ktools/kqueue.rb
51
+ - ext/ktools.c
52
+ - ext/ktools.h
53
+ - ext/epoll.c
54
+ - ext/epoll.h
55
+ - ext/kqueue.c
56
+ - ext/kqueue.h
57
+ - tests/test_epoll.rb
58
+ - tests/test_kqueue.rb
59
+ has_rdoc: true
60
+ homepage: http://www.github.com/yakischloba/ktools
61
+ post_install_message:
62
+ rdoc_options: []
63
+
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ requirements: []
79
+
80
+ rubyforge_project: ktools
81
+ rubygems_version: 1.2.0
82
+ signing_key:
83
+ specification_version: 2
84
+ summary: Bringing common kernel APIs into Ruby using FFI
85
+ test_files: []
86
+