ktools 0.0.1

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,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
+