io_splice 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,156 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # most tasks are in the GNUmakefile which offers better parallelism
4
+
5
+ def tags
6
+ timefmt = '%Y-%m-%dT%H:%M:%SZ'
7
+ @tags ||= `git tag -l`.split(/\n/).map do |tag|
8
+ if %r{\Av[\d\.]+\z} =~ tag
9
+ header, subject, body = `git cat-file tag #{tag}`.split(/\n\n/, 3)
10
+ header = header.split(/\n/)
11
+ tagger = header.grep(/\Atagger /).first
12
+ body ||= "initial"
13
+ {
14
+ :time => Time.at(tagger.split(/ /)[-2].to_i).utc.strftime(timefmt),
15
+ :tagger_name => %r{^tagger ([^<]+)}.match(tagger)[1].strip,
16
+ :tagger_email => %r{<([^>]+)>}.match(tagger)[1].strip,
17
+ :id => `git rev-parse refs/tags/#{tag}`.chomp!,
18
+ :tag => tag,
19
+ :subject => subject,
20
+ :body => body,
21
+ }
22
+ end
23
+ end.compact.sort { |a,b| b[:time] <=> a[:time] }
24
+ end
25
+
26
+ cgit_url = "http://git.bogomips.org/cgit/ruby_io_splice.git"
27
+ git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/ruby_io_splice.git'
28
+ web_url = "http://bogomips.org/ruby_io_splice/"
29
+
30
+ desc 'prints news as an Atom feed'
31
+ task :news_atom do
32
+ require 'nokogiri'
33
+ new_tags = tags[0,10]
34
+ puts(Nokogiri::XML::Builder.new do
35
+ feed :xmlns => "http://www.w3.org/2005/Atom" do
36
+ id! "#{web_url}NEWS.atom.xml"
37
+ title "Ruby io_splice news"
38
+ subtitle "splice and tee Linux syscalls from Ruby"
39
+ link! :rel => "alternate", :type => "text/html",
40
+ :href => "#{web_url}NEWS.html"
41
+ updated(new_tags.empty? ? "1970-01-01T00:00:00Z" : new_tags.first[:time])
42
+ new_tags.each do |tag|
43
+ entry do
44
+ title tag[:subject]
45
+ updated tag[:time]
46
+ published tag[:time]
47
+ author {
48
+ name tag[:tagger_name]
49
+ email tag[:tagger_email]
50
+ }
51
+ url = "#{cgit_url}/tag/?id=#{tag[:tag]}"
52
+ link! :rel => "alternate", :type => "text/html", :href =>url
53
+ id! url
54
+ message_only = tag[:body].split(/\n.+\(\d+\):\n {6}/s).first.strip
55
+ content({:type =>:text}, message_only)
56
+ content(:type =>:xhtml) { pre tag[:body] }
57
+ end
58
+ end
59
+ end
60
+ end.to_xml)
61
+ end
62
+
63
+ desc 'prints RDoc-formatted news'
64
+ task :news_rdoc do
65
+ tags.each do |tag|
66
+ time = tag[:time].tr!('T', ' ').gsub!(/:\d\dZ/, ' UTC')
67
+ puts "=== #{tag[:tag].sub(/^v/, '')} / #{time}"
68
+ puts ""
69
+
70
+ body = tag[:body]
71
+ puts tag[:body].gsub(/^/sm, " ").gsub(/[ \t]+$/sm, "")
72
+ puts ""
73
+ end
74
+ end
75
+
76
+ desc "print release changelog for Rubyforge"
77
+ task :release_changes do
78
+ version = ENV['VERSION'] or abort "VERSION= needed"
79
+ version = "v#{version}"
80
+ vtags = tags.map { |tag| tag[:tag] =~ /\Av/ and tag[:tag] }.sort
81
+ prev = vtags[vtags.index(version) - 1]
82
+ if prev
83
+ system('git', 'diff', '--stat', prev, version) or abort $?
84
+ puts ""
85
+ system('git', 'log', "#{prev}..#{version}") or abort $?
86
+ else
87
+ system('git', 'log', version) or abort $?
88
+ end
89
+ end
90
+
91
+ desc "print release notes for Rubyforge"
92
+ task :release_notes do
93
+ spec = Gem::Specification.load('io_splice.gemspec')
94
+ puts spec.description.strip
95
+ puts ""
96
+ puts "* #{spec.homepage}"
97
+ puts "* #{spec.email}"
98
+ puts "* #{git_url}"
99
+
100
+ _, _, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
101
+ print "\nChanges:\n\n"
102
+ puts body
103
+ end
104
+
105
+ desc "read news article from STDIN and post to rubyforge"
106
+ task :publish_news do
107
+ require 'rubyforge'
108
+ IO.select([STDIN], nil, nil, 1) or abort "E: news must be read from stdin"
109
+ msg = STDIN.readlines
110
+ subject = msg.shift
111
+ blank = msg.shift
112
+ blank == "\n" or abort "no newline after subject!"
113
+ subject.strip!
114
+ body = msg.join("").strip!
115
+
116
+ rf = RubyForge.new.configure
117
+ rf.login
118
+ rf.post_news('qrp', subject, body)
119
+ end
120
+
121
+ desc "post to RAA"
122
+ task :raa_update do
123
+ require 'net/http'
124
+ require 'net/netrc'
125
+ rc = Net::Netrc.locate('io_splice-raa') or abort "~/.netrc not found"
126
+ password = rc.password
127
+
128
+ s = Gem::Specification.load('io_splice.gemspec')
129
+ desc = [ s.description.strip ]
130
+ desc << ""
131
+ desc << "* #{s.email}"
132
+ desc << "* #{git_url}"
133
+ desc << "* #{cgit_url}"
134
+ desc = desc.join("\n")
135
+ uri = URI.parse('http://raa.ruby-lang.org/regist.rhtml')
136
+ form = {
137
+ :name => s.name,
138
+ :short_description => s.summary,
139
+ :version => s.version.to_s,
140
+ :status => 'experimental',
141
+ :owner => s.authors.first,
142
+ :email => s.email,
143
+ :category_major => 'Library',
144
+ :category_minor => 'System',
145
+ :url => s.homepage,
146
+ :download => 'http://rubyforge.org/frs/?group_id=5626',
147
+ :license => 'LGPL', # LGPLv3, actually, but RAA is ancient...
148
+ :description_style => 'Plain',
149
+ :description => desc,
150
+ :pass => password,
151
+ :submit => 'Update',
152
+ }
153
+ res = Net::HTTP.post_form(uri, form)
154
+ p res
155
+ puts res.body
156
+ end
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+
4
+ # Example of using IO.splice to copy a file
5
+ # This can be significantly faster than IO.copy_stream as data
6
+ # is never copied into userspace.
7
+
8
+ require 'io/splice'
9
+
10
+ usage = "#$0 SOURCE DEST"
11
+ source = ARGV.shift or abort usage
12
+ dest = ARGV.shift or abort usage
13
+
14
+ source = File.open(source, 'rb')
15
+ dest = File.open(dest, 'wb')
16
+ source_fd, dest_fd = source.fileno, dest.fileno
17
+
18
+ # We use a pipe as a ring buffer in kernel space.
19
+ # pipes may store up to IO::Splice::PIPE_CAPA bytes
20
+ pipe = IO.pipe
21
+ rfd, wfd = pipe.map { |io| io.fileno }
22
+
23
+ begin
24
+ nread = begin
25
+ # first pull as many bytes as possible into the pipe
26
+ IO.splice(source_fd, nil, wfd, nil, IO::Splice::PIPE_CAPA, 0)
27
+ rescue EOFError
28
+ break
29
+ end
30
+
31
+ # now move the contents of the pipe buffer into the destination file
32
+ # the copied data never enters userspace
33
+ nwritten = IO.splice(rfd, nil, dest_fd, nil, nread, 0)
34
+
35
+ nwritten == nread or
36
+ abort "short splice to destination file: #{nwritten} != #{nread}"
37
+ end while true
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- encoding: binary -*-
3
+
4
+ # An example of using IO.tee, this is a limited version of the standard
5
+ # "tee" utility that requires stdin and stdout to both be pipes.
6
+ require 'io/splice'
7
+
8
+ usage = "filter_prog1 | #$0 DEST | filter_prog2"
9
+ dest = ARGV.shift or abort usage
10
+ $stdin.stat.pipe? or abort "stdin must be a pipe"
11
+ $stdout.stat.pipe? or abort "stdout must be a pipe"
12
+
13
+ dest = File.open(dest, 'wb')
14
+ out_fd = dest.fileno
15
+
16
+ stdin_fd = $stdin.fileno
17
+ stdout_fd = $stdout.fileno
18
+
19
+ begin
20
+ nread = begin
21
+ # "copy" data from stdin to stdout, without consuming stdin
22
+ IO.tee(stdin_fd, stdout_fd, IO::Splice::PIPE_CAPA, 0)
23
+ rescue EOFError
24
+ break
25
+ end
26
+
27
+ # sends data to the file, consumes stdin
28
+ nwritten = IO.splice(stdin_fd, nil, out_fd, nil, nread, 0)
29
+
30
+ nwritten == nread or
31
+ abort "short splice to file: #{nwritten} != #{nread}"
32
+ end while true
@@ -0,0 +1,9 @@
1
+ require 'mkmf'
2
+ $CPPFLAGS << ' -D_GNU_SOURCE=1'
3
+
4
+ have_func('splice', %w(fcntl.h)) or abort 'splice(2) not defined'
5
+ have_func('tee', %w(fcntl.h)) or abort 'tee(2) not defined'
6
+ have_func('rb_thread_blocking_region')
7
+
8
+ dir_config('io_splice')
9
+ create_makefile('io_splice_ext')
@@ -0,0 +1,319 @@
1
+ #include "ruby.h"
2
+ #ifdef HAVE_RUBY_IO_H
3
+ # include "ruby/io.h"
4
+ #else
5
+ # include "rubyio.h"
6
+ #endif
7
+ #include <fcntl.h>
8
+ #include <assert.h>
9
+ #include <sys/uio.h>
10
+ #include <limits.h>
11
+ #include <alloca.h>
12
+
13
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
14
+ # define RUBY_1_8_TRAP_BEG for(;0;)
15
+ # define RUBY_1_8_TRAP_END for(;0;)
16
+ #else
17
+ /* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
18
+ # include <rubysig.h>
19
+ # define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
20
+ typedef void rb_unblock_function_t(void *);
21
+ typedef VALUE rb_blocking_function_t(void *);
22
+ static VALUE
23
+ rb_thread_blocking_region(
24
+ rb_blocking_function_t *fn, void *data1,
25
+ rb_unblock_function_t *ubf, void *data2)
26
+ {
27
+ VALUE rv;
28
+
29
+ assert(RUBY_UBF_IO == ubf && "RUBY_UBF_IO required for emulation");
30
+
31
+ TRAP_BEG;
32
+ rv = fn(data1);
33
+ TRAP_END;
34
+
35
+ return rv;
36
+ }
37
+ # define RUBY_1_8_TRAP_BEG TRAP_BEG
38
+ # define RUBY_1_8_TRAP_END TRAP_END
39
+ #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
40
+
41
+ #ifndef RSTRING_PTR
42
+ # define RSTRING_PTR(s) (RSTRING(s)->ptr)
43
+ #endif
44
+ #ifndef RSTRING_LEN
45
+ # define RSTRING_LEN(s) (RSTRING(s)->len)
46
+ #endif
47
+ #ifndef RARRAY_PTR
48
+ # define RARRAY_PTR(s) (RARRAY(s)->ptr)
49
+ #endif
50
+ #ifndef RARRAY_LEN
51
+ # define RARRAY_LEN(s) (RARRAY(s)->len)
52
+ #endif
53
+
54
+ /*
55
+ * Releases GVL only iff blocking I/O is used.
56
+ * We'll trust programmers who use non-blocking I/O explicitly to
57
+ * want the fastest possible performance without resorting to threads,
58
+ * so releasing and them immediately reacquiring the GVL would be
59
+ * a waste of time.
60
+ */
61
+ static VALUE nb_io_run(rb_blocking_function_t *fn, void *data, unsigned flags)
62
+ {
63
+ if (flags & SPLICE_F_NONBLOCK)
64
+ return fn(data);
65
+ return rb_thread_blocking_region(fn, data, RUBY_UBF_IO, 0);
66
+ }
67
+
68
+ struct splice_args {
69
+ int fd_in;
70
+ off_t *off_in;
71
+ int fd_out;
72
+ off_t *off_out;
73
+ size_t len;
74
+ unsigned flags;
75
+ };
76
+
77
+ static VALUE nogvl_splice(void *ptr)
78
+ {
79
+ struct splice_args *a = ptr;
80
+ long n;
81
+
82
+ /*
83
+ * it's still possible to block because the SPLICE_F_NONBLOCK flag
84
+ * only affects the pipe descriptor, not the non-pipe descriptor.
85
+ * So use TRAP_BEG/TRAP_END (only) to make Ruby 1.8 happy. We also
86
+ * don't want the TRAP_BEG/TRAP_END compatibility layer in 1.9,
87
+ * so we use the 1.8-only versions
88
+ */
89
+ RUBY_1_8_TRAP_BEG;
90
+ n = splice(a->fd_in, a->off_in, a->fd_out, a->off_out,
91
+ a->len, a->flags);
92
+ RUBY_1_8_TRAP_END;
93
+
94
+ return (VALUE)n;
95
+ }
96
+
97
+ /*
98
+ * call-seq:
99
+ * IO.splice(fd_in, off_in, fd_out, off_out, len, flags) => integer
100
+ *
101
+ * Splice +len+ bytes from/to a pipe. Either +fd_in+ or +fd_out+
102
+ * MUST be a pipe. +fd_in+ and +fd_out+ may BOTH be pipes as of
103
+ * Linux 2.6.31 or later.
104
+ *
105
+ * +off_in+ and +off_out+ if non-nil may be used to
106
+ * specify an offset for the non-pipe file descriptor.
107
+ *
108
+ * +flags+ may be a bitmask of the following flags:
109
+ *
110
+ * IO::Splice::F_MOVE, IO::Splice::F_NONBLOCK, IO::Splice::F_MORE
111
+ *
112
+ * Returns the number of bytes spliced.
113
+ * Raises EOFError when +fd_in+ has reached end of file.
114
+ * Raises Errno::EAGAIN if the IO::Splice::F_NONBLOCK flag is set
115
+ * and the pipe has no data to read from or space to write to. May
116
+ * also raise Errno::EAGAIN if the non-pipe descriptor has no data
117
+ * to read from or space to write to.
118
+ *
119
+ * rd, wr = (pipe = IO.pipe).map { |io| io.fileno }
120
+ * src_io, dst_io = File.open("/path/to/src"), File.open("/path/to/dst")
121
+ * src, dst = src_io.fileno, dst_io.fileno
122
+ *
123
+ * nr = IO.splice(src, nil, wr, nil, IO::Splice::PIPE_CAPA, 0)
124
+ * IO.splice(rd, nil, dst, nil, nr, 0)
125
+ *
126
+ * As splice never exposes buffers to userspace, it will not take
127
+ * into account userspace buffering done by Ruby or stdio. It is
128
+ * also not subject to encoding/decoding filters under Ruby 1.9.
129
+ *
130
+ * See manpage for full documentation:
131
+ * http://kernel.org/doc/man-pages/online/pages/man2/splice.2.html
132
+ */
133
+ static VALUE my_splice(VALUE self,
134
+ VALUE fd_in, VALUE off_in,
135
+ VALUE fd_out, VALUE off_out,
136
+ VALUE len, VALUE flags)
137
+ {
138
+ off_t i, o;
139
+ long n;
140
+ struct splice_args a = {
141
+ .off_in = NIL_P(off_in) ? NULL : (i = NUM2OFFT(off_in), &i),
142
+ .off_out = NIL_P(off_out) ? NULL : (o = NUM2OFFT(off_out), &o),
143
+ .fd_in = NUM2INT(fd_in),
144
+ .fd_out = NUM2INT(fd_out),
145
+ .len = (size_t)NUM2ULONG(len),
146
+ .flags = NUM2UINT(flags),
147
+ };
148
+
149
+ n = (long)nb_io_run(nogvl_splice, &a, a.flags);
150
+ if (n == 0)
151
+ rb_eof_error();
152
+ if (n < 0)
153
+ rb_sys_fail("splice");
154
+ return LONG2NUM(n);
155
+ }
156
+
157
+ struct tee_args {
158
+ int fd_in;
159
+ int fd_out;
160
+ size_t len;
161
+ unsigned flags;
162
+ };
163
+
164
+ /* runs without GVL */
165
+ static VALUE nogvl_tee(void *ptr)
166
+ {
167
+ struct tee_args *a = ptr;
168
+
169
+ return (VALUE)tee(a->fd_in, a->fd_out, a->len, a->flags);
170
+ }
171
+
172
+ /*
173
+ * call-seq:
174
+ * IO.tee(fd_in, fd_out, len, flags) => integer
175
+ *
176
+ * Copies up to +len+ bytes of data from +fd_in+ to +fd_out+. +fd_in+
177
+ * and +fd_out+ must both refer to pipe descriptors. +fd_in+ and +fd_out+
178
+ * may not be endpoints of the same pipe.
179
+ *
180
+ * +flags+ may be zero or IO::Splice::F_NONBLOCK
181
+ * Other IO::Splice flags are currently unimplemented or have no effect.
182
+ *
183
+ * Returns the number of bytes duplicated if successful.
184
+ * Raises EOFError when +fd_in+ is closed and emptied.
185
+ * Raises Errno::EAGAIN when +fd_in+ is empty and/or +fd_out+ is full
186
+ * and +flags+ contains IO::Splice::F_NONBLOCK
187
+ *
188
+ * See manpage for full documentation:
189
+ * http://kernel.org/doc/man-pages/online/pages/man2/tee.2.html
190
+ */
191
+ static VALUE my_tee(VALUE self,
192
+ VALUE fd_in, VALUE fd_out,
193
+ VALUE len, VALUE flags)
194
+ {
195
+ long n;
196
+ struct tee_args a = {
197
+ .fd_in = NUM2INT(fd_in),
198
+ .fd_out = NUM2INT(fd_out),
199
+ .len = (size_t)NUM2ULONG(len),
200
+ .flags = NUM2UINT(flags),
201
+ };
202
+
203
+ n = (long)nb_io_run(nogvl_tee, &a, a.flags);
204
+ if (n == 0)
205
+ rb_eof_error();
206
+ if (n < 0)
207
+ rb_sys_fail("tee");
208
+
209
+ return LONG2NUM(n);
210
+ }
211
+
212
+ struct vmsplice_args {
213
+ int fd;
214
+ struct iovec *iov;
215
+ unsigned long nr_segs;
216
+ unsigned flags;
217
+ };
218
+
219
+ static VALUE nogvl_vmsplice(void *ptr)
220
+ {
221
+ struct vmsplice_args *a = ptr;
222
+
223
+ return (VALUE)vmsplice(a->fd, a->iov, a->nr_segs, a->flags);
224
+ }
225
+
226
+ /*
227
+ * call-seq:
228
+ * IO.vmsplice(fd, string_array, flags) => integer
229
+ *
230
+ * Transfers an array of strings into the pipe descriptor given by fd.
231
+ * +fd+ must be the writable end of a pipe.
232
+ *
233
+ * This may allow the kernel to avoid data copies in some cases.
234
+ * but is (probably) of limited usefulness in Ruby.
235
+ *
236
+ * See manpage for full documentation:
237
+ * http://kernel.org/doc/man-pages/online/pages/man2/vmsplice.2.html
238
+ */
239
+ static VALUE my_vmsplice(VALUE self, VALUE fd, VALUE data, VALUE flags)
240
+ {
241
+ long n;
242
+ struct vmsplice_args a;
243
+ struct iovec *tmp;
244
+ VALUE *ary;
245
+
246
+ switch (TYPE(data)) {
247
+ case T_ARRAY:
248
+ ary = RARRAY_PTR(data);
249
+ a.nr_segs = RARRAY_LEN(data);
250
+
251
+ if (a.nr_segs > IOV_MAX)
252
+ rb_raise(rb_eArgError, "array larger than IOV_MAX");
253
+
254
+ a.iov = tmp = alloca(sizeof(struct iovec) * a.nr_segs);
255
+
256
+ for (n = (long)a.nr_segs; --n >= 0; tmp++, ary++) {
257
+ if (TYPE(*ary) != T_STRING)
258
+ rb_raise(rb_eArgError,
259
+ "must be an array of strings");
260
+ tmp->iov_base = RSTRING_PTR(*ary);
261
+ tmp->iov_len = RSTRING_LEN(*ary);
262
+ }
263
+ break;
264
+ default:
265
+ rb_raise(rb_eArgError, "must be an array of strings");
266
+ }
267
+
268
+ a.fd = NUM2INT(fd);
269
+ a.flags = NUM2UINT(flags);
270
+
271
+ n = (long)nb_io_run(nogvl_vmsplice, &a, a.flags);
272
+ if (n < 0)
273
+ rb_sys_fail("vmsplice");
274
+
275
+ return LONG2NUM(n);
276
+ }
277
+
278
+ void Init_io_splice_ext(void)
279
+ {
280
+ VALUE cSplice = rb_define_class_under(rb_cIO, "Splice", rb_cObject);
281
+
282
+ rb_define_singleton_method(rb_cIO, "splice", my_splice, 6);
283
+ rb_define_singleton_method(rb_cIO, "tee", my_tee, 4);
284
+ rb_define_singleton_method(rb_cIO, "vmsplice", my_vmsplice, 3);
285
+
286
+ /*
287
+ * Attempt to move pages instead of copying. This is only a hint
288
+ * and support for it was removed in Linux 2.6.21 and has not been
289
+ * readded as of 2.6.30.
290
+ */
291
+ rb_define_const(cSplice, "F_MOVE", UINT2NUM(SPLICE_F_MOVE));
292
+
293
+ /*
294
+ * Do not block on I/O. This flag only affects the pipe(s) being
295
+ * spliced from/to and has no effect on the non-pipe descriptor
296
+ * (which requires non-blocking operation to be set explicitly).
297
+ */
298
+ rb_define_const(cSplice, "F_NONBLOCK", UINT2NUM(SPLICE_F_NONBLOCK));
299
+
300
+ /*
301
+ * Indicate that there may be more data coming into the outbound
302
+ * descriptor. This can allow the kernel to avoid sending partial
303
+ * frames from sockets. Currently only used with splice.
304
+ */
305
+ rb_define_const(cSplice, "F_MORE", UINT2NUM(SPLICE_F_MORE));
306
+
307
+ /*
308
+ * Only usable by vmsplice. This flag probably not useful in the
309
+ * context of Ruby applications which cannot control alignment.
310
+ */
311
+ rb_define_const(cSplice, "F_GIFT", UINT2NUM(SPLICE_F_GIFT));
312
+
313
+ /*
314
+ * The maximum size of an atomic write to a pipe
315
+ * POSIX requires this to be at least 512 bytes.
316
+ * Under Linux, this is 4096 bytes.
317
+ */
318
+ rb_define_const(cSplice, "PIPE_BUF", UINT2NUM(PIPE_BUF));
319
+ }