ktools 0.0.1

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,75 @@
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
+ sh "bacon tests/test_kqueue.rb" if Kernel.have_kqueue?
62
+ sh "bacon tests/test_epoll.rb" if Kernel.have_epoll?
63
+ end
64
+
65
+ task :objs do
66
+ chdir "ext" do
67
+ $ktools_srcs.each {|c| sh "#{$ktools_cc} #{$ktools_cflags} #{$ktools_defines.join(' ')} #{$ktools_includes} -c #{c}"}
68
+ end
69
+ end
70
+
71
+ task :shared do
72
+ chdir "ext" do
73
+ sh "#{$ktools_ldshared}"
74
+ end
75
+ 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,30 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "ktools"
3
+ s.version = "0.0.1"
4
+ s.date = "2009-04-21"
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.summary = "Bringing common kernel APIs into Ruby using FFI"
11
+ s.homepage = "http://www.github.com/yakischloba/ktools"
12
+ s.description = "Bringing common kernel APIs into Ruby using FFI"
13
+ s.extensions = ["Rakefile"]
14
+ s.files =
15
+ ["ktools.gemspec",
16
+ "README",
17
+ "Rakefile",
18
+ "lib/ktools.rb",
19
+ "lib/ktools/ktools.rb",
20
+ "lib/ktools/epoll.rb",
21
+ "lib/ktools/kqueue.rb",
22
+ "ext/ktools.c",
23
+ "ext/ktools.h",
24
+ "ext/epoll.c",
25
+ "ext/epoll.h",
26
+ "ext/kqueue.c",
27
+ "ext/kqueue.h",
28
+ "tests/test_epoll.rb",
29
+ "tests/test_kqueue.rb"]
30
+ 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,45 @@
1
+ module Kernel
2
+ module 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, Epoll_data
15
+ end
16
+
17
+ epc = FFI::ConstGenerator.new do |c|
18
+ c.include('sys/epoll.h')
19
+ c.const("EPOLLIN")
20
+ c.const("EPOLLPRI")
21
+ c.const("EPOLLOUT")
22
+ c.const("EPOLLRDNORM")
23
+ c.const("EPOLLRDBAND")
24
+ c.const("EPOLLWRNORM")
25
+ c.const("EPOLLWRBAND")
26
+ c.const("EPOLLMSG")
27
+ c.const("EPOLLERR")
28
+ c.const("EPOLLHUP")
29
+ c.const("EPOLLONESHOT")
30
+ c.const("EPOLLET")
31
+ c.const("EPOLL_CTL_ADD")
32
+ c.const("EPOLL_CTL_DEL")
33
+ c.const("EPOLL_CTL_MOD")
34
+ end
35
+
36
+ eval epc.to_ruby
37
+
38
+ # Attach directly to epoll_create
39
+ attach_function :epoll_create, [:int], :int
40
+ # Attach directly to epoll_ctl
41
+ attach_function :epoll_ctl, [:int, :int, :int, :pointer], :int
42
+ # Attach to the epoll_wait wrapper so we can use rb_thread_blocking_region when possible
43
+ attach_function :epoll_wait, :wrap_epoll_wait, [:int, :pointer, :int, :int], :int
44
+ end
45
+ end
@@ -0,0 +1,378 @@
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 object. 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
+ end
141
+ end
142
+
143
+ # Add events on a file to the Kqueue. kqueue requires that a file actually be opened (to get a descriptor)
144
+ # before it can be monitored. You can pass a File object here, or a String of the pathname, in which
145
+ # case we'll try to open the file for you. In either case, a File object will be returned as the :target
146
+ # in the event returned by #poll. Valid events here are as follows, using descriptions from the kqueue man pages:
147
+ #
148
+ # * :delete - "The unlink() system call was called on the file referenced by the descriptor."
149
+ # * :write - "A write occurred on the file referenced by the descriptor."
150
+ # * :extend - "The file referenced by the descriptor was extended."
151
+ # * :attrib - "The file referenced by the descriptor had its attributes changed."
152
+ # * :link - "The link count on the file changed."
153
+ # * :rename - "The file referenced by the descriptor was renamed."
154
+ # * :revoke - "Access to the file was revoked via revoke(2) or the underlying fileystem was unmounted."
155
+ #
156
+ # Example:
157
+ # irb(main):001:0> require 'ktools'
158
+ # => true
159
+ # irb(main):002:0> file = Tempfile.new("kqueue-test")
160
+ # => #<File:/tmp/kqueue-test20090417-602-evm5wc-0>
161
+ # irb(main):003:0> kq = Kqueue.new
162
+ # => #<Kernel::Kqueue:0x4f0aec @kqfd=5, @fds={}>
163
+ # irb(main):004:0> kq.add(:file, file, :events => [:write, :delete])
164
+ # => true
165
+ # irb(main):005:0> kq.poll
166
+ # => []
167
+ # irb(main):006:0> file.delete
168
+ # => #<File:/tmp/kqueue-test20090417-602-evm5wc-0>
169
+ # irb(main):007:0> kq.poll
170
+ # => [{:type=>:file, :target=>#<File:/tmp/kqueue-test20090417-602-evm5wc-0>, :event=>:delete}]
171
+ # irb(main):008:0> file.close and kq.close
172
+ # => nil
173
+ def add_file(file, options={})
174
+ fflags, flags = options.values_at :events, :flags
175
+ raise ArgumentError.new("must specify which file events to watch for") unless fflags
176
+
177
+ file = file.kind_of?(File) || file.kind_of?(Tempfile) ? file : File.open(file, 'r')
178
+
179
+ k = Kevent.new
180
+ flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
181
+ fflags = fflags.inject(0){|m,i| m | KQ_FFLAGS[i] }
182
+ ev_set(k, file.fileno, EVFILT_VNODE, EV_ADD | flags, fflags, 0, nil)
183
+
184
+ if kevent(@kqfd, k, 1, nil, 0, nil) == -1
185
+ return false
186
+ else
187
+ @fds[file.fileno] = {:target => file, :kevent => k}
188
+ return true
189
+ end
190
+ end
191
+
192
+ # Add events to a socket-style descriptor (socket or pipe). Supported events are:
193
+ #
194
+ # * :read - The descriptor has become readable.
195
+ # * :write - The descriptor has become writeable.
196
+ #
197
+ # See the kqueue manpage for how behavior differs depending on the descriptor types.
198
+ # In general, you shouldn't have to worry about it.
199
+ #
200
+ # Example:
201
+ # irb(main):001:0> require 'ktools'
202
+ # => true
203
+ # irb(main):002:0> r, w = IO.pipe
204
+ # => [#<IO:0x4fa90c>, #<IO:0x4fa880>]
205
+ # irb(main):003:0> kq = Kqueue.new
206
+ # => #<Kernel::Kqueue:0x4f43a4 @kqfd=6, @fds={}>
207
+ # irb(main):004:0> kq.add(:socket, r, :events => [:read, :write])
208
+ # => true
209
+ # irb(main):005:0> kq.poll
210
+ # => []
211
+ # irb(main):006:0> w.write "foo"
212
+ # => 3
213
+ # irb(main):007:0> kq.poll
214
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
215
+ # irb(main):008:0> [r, w, kq].each {|i| i.close}
216
+ def add_socket(io, options={})
217
+ filters, flags = options.values_at :events, :flags
218
+ flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
219
+ filters = filters ? filters.inject(0){|m,i| m | KQ_FILTERS[i] } : EVFILT_READ | EVFILT_WRITE
220
+
221
+ k = Kevent.new
222
+ ev_set(k, io.fileno, filters, EV_ADD | flags, 0, 0, nil)
223
+
224
+ if kevent(@kqfd, k, 1, nil, 0, nil) == -1
225
+ return false
226
+ else
227
+ @fds[io.fileno] = {:target => io, :kevent => k}
228
+ return true
229
+ end
230
+ end
231
+
232
+ # Add events for a process. Takes a process id and and options hash. Supported events are:
233
+ # * :exit - The process has exited
234
+ # * :fork - The process has created a child process via fork(2) or similar call.
235
+ # * :exec - The process executed a new process via execve(2) or similar call.
236
+ # * :signal - The process was sent a signal. Status can be checked via waitpid(2) or similar call.
237
+ # * :reap - The process was reaped by the parent via wait(2) or similar call.\
238
+ #
239
+ # Note: SIGNAL and REAP do not appear to exist in OSX older than Leopard.
240
+ #
241
+ # Example:
242
+ # irb(main):001:0> require 'ktools'
243
+ # => true
244
+ # irb(main):002:0> kq = Kqueue.new
245
+ # => #<Kernel::Kqueue:0x14f55b4 @kqfd=4, @pids={}, @fds={}>
246
+ # irb(main):003:0> fpid = fork{ sleep }
247
+ # => 616
248
+ # irb(main):004:0> kq.add(:process, fpid, :events => [:exit])
249
+ # => true
250
+ # irb(main):005:0> Process.kill('TERM', fpid)
251
+ # => 1
252
+ # irb(main):006:0> kq.poll.first
253
+ # => {:event=>:exit, :type=>:process, :target=>616}
254
+ #
255
+ def add_process(pid, options={})
256
+ flags, fflags = options.values_at :flags, :events
257
+ flags = flags ? flags.inject(0){|m,i| m | KQ_FLAGS[i] } : EV_CLEAR
258
+ fflags = fflags.inject(0){|m,i| m | KQ_FFLAGS[i] }
259
+
260
+ k = Kevent.new
261
+ ev_set(k, pid, EVFILT_PROC, EV_ADD | flags, fflags, 0, nil)
262
+
263
+ if kevent(@kqfd, k, 1, nil, 0, nil) == -1
264
+ return false
265
+ else
266
+ @pids[pid] = {:target => pid, :kevent => k}
267
+ return true
268
+ end
269
+ end
270
+
271
+ # 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).
272
+ #
273
+ # Using a timeout will block for the duration of the timeout. Under Ruby 1.9.1, we use rb_thread_blocking_region() under the
274
+ # 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
275
+ # will block the whole interpreter (all threads) until it returns.
276
+ #
277
+ # This call returns an array of hashes, similar to the following:
278
+ # => [{:type=>:socket, :target=>#<IO:0x4fa90c>, :event=>:read}]
279
+ #
280
+ # * :type - will be the type of event target, i.e. an event set with #add_file will have :type => :file
281
+ # * :target - the 'target' or 'subject' of the event. This can be a File, IO, process or signal number.
282
+ # * :event - the event that occurred on the target. This is one of the symbols you passed as :events => [:foo] when adding the event.
283
+ def poll(timeout=0.0)
284
+ k = Kevent.new
285
+ t = Timespec.new
286
+ t[:tv_sec] = timeout.to_i
287
+ t[:tv_nsec] = ((timeout - timeout.to_i) * 1e9).to_i
288
+
289
+ case kevent(@kqfd, nil, 0, k, 1, t)
290
+ when -1
291
+ [errno]
292
+ when 0
293
+ []
294
+ else
295
+ [process_event(k)]
296
+ end
297
+ end
298
+
299
+ def process_event(k) #:nodoc:
300
+ res = case k[:filter]
301
+ when EVFILT_VNODE
302
+ h = @fds[k[:ident]]
303
+ return nil if h.nil?
304
+ event = if k[:fflags] & NOTE_DELETE == NOTE_DELETE
305
+ :delete
306
+ elsif k[:fflags] & NOTE_WRITE == NOTE_WRITE
307
+ :write
308
+ elsif k[:fflags] & NOTE_EXTEND == NOTE_EXTEND
309
+ :extend
310
+ elsif k[:fflags] & NOTE_ATTRIB == NOTE_ATTRIB
311
+ :attrib
312
+ elsif k[:fflags] & NOTE_LINK == NOTE_LINK
313
+ :link
314
+ elsif k[:fflags] & NOTE_RENAME == NOTE_RENAME
315
+ :rename
316
+ elsif k[:fflags] & NOTE_REVOKE == NOTE_REVOKE
317
+ :revoke
318
+ end
319
+ delete(:file, k[:ident]) if event == :delete || event == :revoke
320
+ {:target => h[:target], :type => :file, :event => event}
321
+ when EVFILT_READ
322
+ h = @fds[k[:ident]]
323
+ return nil if h.nil?
324
+ {:target => h[:target], :type => :socket, :event => :read}
325
+ when EVFILT_WRITE
326
+ h = @fds[k[:ident]]
327
+ return nil if h.nil?
328
+ {:target => h[:target], :type => :socket, :event => :write}
329
+ when EVFILT_PROC
330
+ h = @pids[k[:ident]]
331
+ return nil if h.nil?
332
+ event = if k[:fflags] & NOTE_EXIT == NOTE_EXIT
333
+ :exit
334
+ elsif k[:fflags] & NOTE_FORK == NOTE_FORK
335
+ :fork
336
+ elsif k[:fflags] & NOTE_EXEC == NOTE_EXEC
337
+ :exec
338
+ elsif Kqueue.const_defined?("NOTE_SIGNAL") and k[:fflags] & NOTE_SIGNAL == NOTE_SIGNAL
339
+ :signal
340
+ elsif Kqueue.const_defined?("NOTE_REAP") and k[:fflags] & NOTE_REAP == NOTE_REAP
341
+ :reap
342
+ end
343
+ delete(:process, k[:ident]) if event == :exit
344
+ {:target => h[:target], :type => :process, :event => event}
345
+ end
346
+
347
+ delete(res[:type], res[:target]) if k[:flags] & EV_ONESHOT == EV_ONESHOT
348
+ res
349
+ end
350
+
351
+ # Stop generating events for the given type and event target, ie:
352
+ # kq.delete(:process, 6244)
353
+ def delete(type, target)
354
+ ident = target.respond_to?(:fileno) ? target.fileno : target
355
+ container = case type
356
+ when :socket
357
+ @fds
358
+ when :file
359
+ @fds
360
+ when :process
361
+ @pids
362
+ end
363
+ h = container[ident]
364
+ return false if h.nil?
365
+ k = h[:kevent]
366
+ ev_set(k, k[:ident], k[:filter], EV_DELETE, k[:fflags], 0, nil)
367
+ kevent(@kqfd, k, 1, nil, 0, nil)
368
+ container.delete(ident)
369
+ return true
370
+ end
371
+
372
+ # Close the kqueue descriptor. This essentially shuts down your kqueue and renders all active events on this kqueue removed.
373
+ def close
374
+ IO.for_fd(@kqfd).close
375
+ end
376
+
377
+ end
378
+ 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
@@ -0,0 +1,42 @@
1
+ require 'lib/ktools'
2
+ require 'bacon'
3
+
4
+ include Kernel::Epoll
5
+
6
+ describe "the epoll interface" do
7
+
8
+ it "should return a valid epoll file descriptor" do
9
+ @epfd = epoll_create(10)
10
+ @epfd.class.should.equal Fixnum
11
+ @epfd.should.be > 0
12
+ end
13
+
14
+ it "should set an event for readable status of a descriptor, and retrieve the event when it occurs" do
15
+ r, w = IO.pipe
16
+ ev = Epoll_event.new
17
+ ev[:events] = EPOLLIN
18
+ ev[:data] = Epoll_data.new
19
+ ev[:data][:fd] = r.fileno
20
+ ev[:data][:u32] = 12345
21
+ epoll_ctl(@epfd, EPOLL_CTL_ADD, r.fileno, ev).should.equal 0
22
+ rev = Epoll_event.new
23
+ epoll_wait(@epfd, rev, 1, 50).should.equal 0
24
+ w.write "foo"
25
+ epoll_wait(@epfd, rev, 1, 50).should.equal 1
26
+ rev[:events].should.equal EPOLLIN
27
+ rev[:data][:fd].should.equal r.fileno
28
+ rev[:data][:u32].should.equal 12345
29
+ w.close
30
+ r.close
31
+ w.should.be.closed
32
+ r.should.be.closed
33
+ end
34
+
35
+ it "should close the epoll file descriptor" do
36
+ i = IO.for_fd(@epfd)
37
+ i.should.not.be.closed
38
+ i.close
39
+ i.should.be.closed
40
+ end
41
+
42
+ end
@@ -0,0 +1,112 @@
1
+ require 'lib/ktools'
2
+ require 'bacon'
3
+
4
+ describe "the kqueue interface" do
5
+
6
+ it "should return a valid kqueue file descriptor" do
7
+ @kqfd = Kqueue::kqueue
8
+ @kqfd.class.should.equal Fixnum
9
+ @kqfd.should.be > 0
10
+ end
11
+
12
+ it "should use the raw C API to set an event for file watching, and retrieve the event when it occurs" do
13
+ file = Tempfile.new("kqueue-test")
14
+ k = Kqueue::Kevent.new
15
+ k.should.not.be.nil
16
+ Kqueue::ev_set(k, file.fileno, Kqueue::EVFILT_VNODE, Kqueue::EV_ADD | Kqueue::EV_CLEAR, Kqueue::NOTE_WRITE, 0, nil)
17
+ Kqueue::kevent(@kqfd, k, 1, nil, 0, nil)
18
+ File.open(file.path, 'w'){|x| x.puts 'foo'}
19
+ n = Kqueue::Kevent.new
20
+ res = Kqueue::kevent(@kqfd, nil, 0, n, 1, nil)
21
+ res.should.be > -1
22
+ n[:ident].should.equal file.fileno
23
+ n[:filter].should.equal Kqueue::EVFILT_VNODE
24
+ n[:fflags].should.equal Kqueue::NOTE_WRITE
25
+ file.close
26
+ end
27
+
28
+ it "should close the kqueue file descriptor" do
29
+ i = IO.for_fd(@kqfd)
30
+ i.should.not.be.closed
31
+ i.close
32
+ i.should.be.closed
33
+ end
34
+
35
+ it "should add file events using the ruby API" do
36
+ file = Tempfile.new("kqueue-test")
37
+ kq = Kqueue.new
38
+ kq.add(:file, file, :events => [:write, :delete]).should.be.true
39
+
40
+ kq.poll.should.be.empty
41
+ File.open(file.path, 'w'){|x| x.puts 'foo'}
42
+
43
+ res = kq.poll.first
44
+ res.class.should.equal Hash
45
+ res[:target].class.should.equal Tempfile
46
+ res[:target].fileno.should.equal file.fileno
47
+ res[:type].should.equal :file
48
+ res[:event].should.equal :write
49
+
50
+ kq.poll.should.be.empty
51
+ file.delete
52
+
53
+ res2 = kq.poll.first
54
+ res2[:target].class.should.equal Tempfile
55
+ res2[:target].fileno.should.equal file.fileno
56
+ res2[:type].should.equal :file
57
+ res2[:event].should.equal :delete
58
+
59
+ file.close
60
+ kq.close
61
+ end
62
+
63
+ it "should add events for socket-style descriptors, then delete them, using the Ruby API" do
64
+ r, w = IO.pipe
65
+ kq = Kqueue.new
66
+ kq.add(:socket, r, :events => [:read]).should.be.true
67
+
68
+ kq.poll.should.be.empty
69
+ w.write "foo"
70
+
71
+ res = kq.poll.first
72
+ res[:target].class.should.equal IO
73
+ res[:target].fileno.should.equal r.fileno
74
+ res[:type].should.equal :socket
75
+ res[:event].should.equal :read
76
+
77
+ kq.poll.should.be.empty
78
+ kq.delete(:socket, r).should.be.true
79
+ w.write "foo"
80
+ kq.poll.should.be.empty
81
+
82
+ [r,w,kq].each{|i| i.close}
83
+ end
84
+
85
+ it "should add events for a process using the Ruby API" do
86
+ kq = Kqueue.new
87
+ # Watch for ourself to fork
88
+ kq.add(:process, Process.pid, :events => [:fork]).should.be.true
89
+
90
+ fpid = fork{ at_exit {exit!}; sleep }
91
+
92
+ res = kq.poll(1).first
93
+ res[:target].should.equal Process.pid
94
+ res[:type].should.equal :process
95
+ res[:event].should.equal :fork
96
+
97
+ # Watch for the child to exit and kill it
98
+ kq.add(:process, fpid, :events => [:exit])
99
+ sleep 0.5
100
+ Process.kill('TERM', fpid)
101
+
102
+ res2 = kq.poll(1).first
103
+ res2[:target].should.equal fpid
104
+ res2[:type].should.equal :process
105
+ res2[:event].should.equal :exit
106
+
107
+ kq.poll.should.be.empty
108
+
109
+ kq.close
110
+ end
111
+
112
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ktools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jake Douglas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-21 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
+ description: Bringing common kernel APIs into Ruby using FFI
26
+ email: jakecdouglas@gmail.com
27
+ executables: []
28
+
29
+ extensions:
30
+ - Rakefile
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - ktools.gemspec
35
+ - README
36
+ - Rakefile
37
+ - lib/ktools.rb
38
+ - lib/ktools/ktools.rb
39
+ - lib/ktools/epoll.rb
40
+ - lib/ktools/kqueue.rb
41
+ - ext/ktools.c
42
+ - ext/ktools.h
43
+ - ext/epoll.c
44
+ - ext/epoll.h
45
+ - ext/kqueue.c
46
+ - ext/kqueue.h
47
+ - tests/test_epoll.rb
48
+ - tests/test_kqueue.rb
49
+ has_rdoc: true
50
+ homepage: http://www.github.com/yakischloba/ktools
51
+ post_install_message:
52
+ rdoc_options: []
53
+
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ requirements: []
69
+
70
+ rubyforge_project: ktools
71
+ rubygems_version: 1.3.1
72
+ signing_key:
73
+ specification_version: 2
74
+ summary: Bringing common kernel APIs into Ruby using FFI
75
+ test_files: []
76
+