yakischloba-ktools 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+