io_splice 0.1.0
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/.document +6 -0
- data/.gitignore +14 -0
- data/COPYING +165 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +169 -0
- data/LICENSE +16 -0
- data/README +87 -0
- data/Rakefile +156 -0
- data/examples/splice-cp.rb +37 -0
- data/examples/splice-tee.rb +32 -0
- data/ext/io_splice/extconf.rb +9 -0
- data/ext/io_splice/io_splice_ext.c +319 -0
- data/io_splice.gemspec +38 -0
- data/lib/io/splice.rb +30 -0
- data/local.mk.sample +64 -0
- data/setup.rb +1586 -0
- data/test/test_io_splice.rb +128 -0
- metadata +86 -0
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
|
+
}
|