io_splice 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +6 -3
- data/LICENSE +2 -2
- data/Rakefile +2 -2
- data/examples/splice-cp.rb +1 -25
- data/examples/splice-tee.rb +2 -6
- data/ext/io_splice/extconf.rb +2 -0
- data/ext/io_splice/io_splice_ext.c +32 -5
- data/io_splice.gemspec +1 -1
- data/lib/io/splice.rb +70 -3
- data/test/test_io_splice.rb +59 -0
- metadata +2 -2
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -188,7 +188,10 @@ all:: test
|
|
188
188
|
|
189
189
|
build: $(ext)
|
190
190
|
test: test-unit
|
191
|
-
test-unit: build
|
192
|
-
$(RUBY) -I lib:ext/io_splice test/test_io_splice.rb
|
193
191
|
|
194
|
-
|
192
|
+
test_unit := $(wildcard test/test_*.rb)
|
193
|
+
$(test_unit): build
|
194
|
+
$(RUBY) -I lib:ext/io_splice $@
|
195
|
+
test-unit: $(test_unit)
|
196
|
+
|
197
|
+
.PHONY: .FORCE-GIT-VERSION-FILE doc manifest man test $(test_unit)
|
data/LICENSE
CHANGED
@@ -3,8 +3,8 @@ revision control for names and email addresses of all of them.
|
|
3
3
|
|
4
4
|
You can redistribute it and/or modify it under the terms of the GNU
|
5
5
|
Lesser General Public License as published by the Free Software Foundation,
|
6
|
-
version
|
7
|
-
link:COPYING).
|
6
|
+
version 2.1 or later {LGPLv2.1}[http://www.gnu.org/licenses/lgpl-2.1.txt]
|
7
|
+
(see link:COPYING).
|
8
8
|
|
9
9
|
io_splice is distributed in the hope that it will be useful, but WITHOUT
|
10
10
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
data/Rakefile
CHANGED
@@ -137,14 +137,14 @@ task :raa_update do
|
|
137
137
|
:name => s.name,
|
138
138
|
:short_description => s.summary,
|
139
139
|
:version => s.version.to_s,
|
140
|
-
:status => '
|
140
|
+
:status => 'stable',
|
141
141
|
:owner => s.authors.first,
|
142
142
|
:email => s.email,
|
143
143
|
:category_major => 'Library',
|
144
144
|
:category_minor => 'System',
|
145
145
|
:url => s.homepage,
|
146
146
|
:download => 'http://rubyforge.org/frs/?group_id=5626',
|
147
|
-
:license => 'LGPL',
|
147
|
+
:license => 'LGPL',
|
148
148
|
:description_style => 'Plain',
|
149
149
|
:description => desc,
|
150
150
|
:pass => password,
|
data/examples/splice-cp.rb
CHANGED
@@ -10,28 +10,4 @@ require 'io/splice'
|
|
10
10
|
usage = "#$0 SOURCE DEST"
|
11
11
|
source = ARGV.shift or abort usage
|
12
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
|
13
|
+
IO::Splice.copy_stream(source, dest)
|
data/examples/splice-tee.rb
CHANGED
@@ -11,21 +11,17 @@ $stdin.stat.pipe? or abort "stdin must be a pipe"
|
|
11
11
|
$stdout.stat.pipe? or abort "stdout must be a pipe"
|
12
12
|
|
13
13
|
dest = File.open(dest, 'wb')
|
14
|
-
out_fd = dest.fileno
|
15
|
-
|
16
|
-
stdin_fd = $stdin.fileno
|
17
|
-
stdout_fd = $stdout.fileno
|
18
14
|
|
19
15
|
begin
|
20
16
|
nread = begin
|
21
17
|
# "copy" data from stdin to stdout, without consuming stdin
|
22
|
-
IO.tee(
|
18
|
+
IO.tee($stdin, $stdout, IO::Splice::PIPE_CAPA, 0)
|
23
19
|
rescue EOFError
|
24
20
|
break
|
25
21
|
end
|
26
22
|
|
27
23
|
# sends data to the file, consumes stdin
|
28
|
-
nwritten = IO.splice(
|
24
|
+
nwritten = IO.splice($stdin, nil, dest, nil, nread, 0)
|
29
25
|
|
30
26
|
nwritten == nread or
|
31
27
|
abort "short splice to file: #{nwritten} != #{nread}"
|
data/ext/io_splice/extconf.rb
CHANGED
@@ -4,6 +4,8 @@ $CPPFLAGS << ' -D_GNU_SOURCE=1'
|
|
4
4
|
have_func('splice', %w(fcntl.h)) or abort 'splice(2) not defined'
|
5
5
|
have_func('tee', %w(fcntl.h)) or abort 'tee(2) not defined'
|
6
6
|
have_func('rb_thread_blocking_region')
|
7
|
+
have_macro('F_GETPIPE_SZ', %w(fcntl.h))
|
8
|
+
have_macro('F_SETPIPE_SZ', %w(fcntl.h))
|
7
9
|
|
8
10
|
dir_config('io_splice')
|
9
11
|
create_makefile('io_splice_ext')
|
@@ -4,6 +4,7 @@
|
|
4
4
|
#else
|
5
5
|
# include "rubyio.h"
|
6
6
|
#endif
|
7
|
+
#include <errno.h>
|
7
8
|
#include <fcntl.h>
|
8
9
|
#include <assert.h>
|
9
10
|
#include <sys/uio.h>
|
@@ -225,7 +226,7 @@ static VALUE my_tee(VALUE self,
|
|
225
226
|
|
226
227
|
struct vmsplice_args {
|
227
228
|
int fd;
|
228
|
-
|
229
|
+
struct iovec *iov;
|
229
230
|
unsigned long nr_segs;
|
230
231
|
unsigned flags;
|
231
232
|
};
|
@@ -243,7 +244,6 @@ do { \
|
|
243
244
|
VALUE *cur; \
|
244
245
|
struct iovec *tmp; \
|
245
246
|
long n; \
|
246
|
-
Check_Type(ary, T_ARRAY); \
|
247
247
|
cur = RARRAY_PTR(ary); \
|
248
248
|
n = RARRAY_LEN(ary); \
|
249
249
|
if (n > IOV_MAX) \
|
@@ -292,6 +292,7 @@ static void advance_vmsplice_args(struct vmsplice_args *a, long n)
|
|
292
292
|
/*
|
293
293
|
* call-seq:
|
294
294
|
* IO.vmsplice(fd, string_array, flags) => integer
|
295
|
+
* IO.vmsplice(fd, string, flags) => integer
|
295
296
|
*
|
296
297
|
* Transfers an array of strings into the pipe descriptor given by fd.
|
297
298
|
* +fd+ must be the writable end of a pipe.
|
@@ -308,7 +309,24 @@ static VALUE my_vmsplice(VALUE self, VALUE fd, VALUE data, VALUE flags)
|
|
308
309
|
ssize_t left;
|
309
310
|
struct vmsplice_args a;
|
310
311
|
|
311
|
-
|
312
|
+
switch (TYPE(data)) {
|
313
|
+
case T_STRING: {
|
314
|
+
struct iovec iov;
|
315
|
+
|
316
|
+
iov.iov_base = RSTRING_PTR(data);
|
317
|
+
iov.iov_len = (size_t)(left = (ssize_t)RSTRING_LEN(data));
|
318
|
+
a.iov = &iov;
|
319
|
+
a.nr_segs = 1;
|
320
|
+
}
|
321
|
+
break;
|
322
|
+
case T_ARRAY:
|
323
|
+
ARY2IOVEC(a.iov, a.nr_segs, left, data);
|
324
|
+
break;
|
325
|
+
default:
|
326
|
+
rb_raise(rb_eTypeError, "wrong argument type %s "
|
327
|
+
"(expected a String or Array of strings)",
|
328
|
+
rb_obj_classname(data));
|
329
|
+
}
|
312
330
|
a.fd = my_fileno(fd);
|
313
331
|
a.flags = NUM2UINT(flags);
|
314
332
|
|
@@ -353,8 +371,8 @@ void Init_io_splice_ext(void)
|
|
353
371
|
|
354
372
|
/*
|
355
373
|
* Attempt to move pages instead of copying. This is only a hint
|
356
|
-
* and support for it was removed in Linux 2.6.21
|
357
|
-
|
374
|
+
* and support for it was removed in Linux 2.6.21. It will be
|
375
|
+
* re-added for FUSE filesystems only in Linux 2.6.35.
|
358
376
|
*/
|
359
377
|
rb_define_const(mSplice, "F_MOVE", UINT2NUM(SPLICE_F_MOVE));
|
360
378
|
|
@@ -384,6 +402,15 @@ void Init_io_splice_ext(void)
|
|
384
402
|
*/
|
385
403
|
rb_define_const(mSplice, "F_GIFT", UINT2NUM(SPLICE_F_GIFT));
|
386
404
|
|
405
|
+
#ifdef F_GETPIPE_SZ
|
406
|
+
/* :nodoc: */
|
407
|
+
rb_define_const(mSplice, "F_GETPIPE_SZ", UINT2NUM(F_GETPIPE_SZ));
|
408
|
+
#endif
|
409
|
+
#ifdef F_SETPIPE_SZ
|
410
|
+
/* :nodoc: */
|
411
|
+
rb_define_const(mSplice, "F_SETPIPE_SZ", UINT2NUM(F_SETPIPE_SZ));
|
412
|
+
#endif
|
413
|
+
|
387
414
|
/*
|
388
415
|
* The maximum size of an atomic write to a pipe
|
389
416
|
* POSIX requires this to be at least 512 bytes.
|
data/io_splice.gemspec
CHANGED
data/lib/io/splice.rb
CHANGED
@@ -5,13 +5,16 @@ class IO
|
|
5
5
|
|
6
6
|
module Splice
|
7
7
|
|
8
|
-
# the version of IO::Splice, currently 0.
|
9
|
-
VERSION = '
|
8
|
+
# the version of IO::Splice, currently 2.0.0
|
9
|
+
VERSION = '2.0.0'
|
10
10
|
|
11
|
-
# The maximum capacity of the pipe in bytes.
|
11
|
+
# The maximum default capacity of the pipe in bytes.
|
12
12
|
# Under stock Linux, this is 65536 bytes as of 2.6.11, and 4096 before
|
13
13
|
# We detect this at runtime as it is easy to recompile the kernel
|
14
14
|
# and set a new value.
|
15
|
+
# Starting with Linux 2.6.35, pipe capacity will be tunable
|
16
|
+
# and this will only represent the default capacity of a
|
17
|
+
# newly-created pipe.
|
15
18
|
PIPE_CAPA = begin
|
16
19
|
rd, wr = IO.pipe
|
17
20
|
buf = ' ' * PIPE_BUF
|
@@ -26,5 +29,69 @@ class IO
|
|
26
29
|
n
|
27
30
|
end
|
28
31
|
|
32
|
+
# copies the contents of the IO object given by +src+ to +dst+
|
33
|
+
# If len is specified, then only len bytes are copied. Otherwise
|
34
|
+
# the copy will be until EOF is reached on the +src+.
|
35
|
+
# +src+ and +dst+ must be IO objects or respond to +to_io+
|
36
|
+
def self.copy_stream(src, dst, len = nil, src_offset = nil)
|
37
|
+
src.kind_of?(String) and src = File.open(src, 'rb')
|
38
|
+
dst.kind_of?(String) and dst = File.open(dst, 'wb')
|
39
|
+
src, dst = src.to_io, dst.to_io
|
40
|
+
rv = len
|
41
|
+
src.sysseek(src_offset) if src_offset
|
42
|
+
if src.stat.pipe? || dst.stat.pipe?
|
43
|
+
if len
|
44
|
+
while len > 0
|
45
|
+
nr = len > PIPE_CAPA ? PIPE_CAPA : len
|
46
|
+
nr = IO.splice(src, nil, dst, nil, nr, F_MOVE)
|
47
|
+
if nr == 0
|
48
|
+
raise EOFError, "unexpected EOF with #{len} bytes left"
|
49
|
+
end
|
50
|
+
len -= nr
|
51
|
+
end
|
52
|
+
else
|
53
|
+
rv = 0
|
54
|
+
begin
|
55
|
+
nr = IO.splice(src, nil, dst, nil, PIPE_CAPA, F_MOVE)
|
56
|
+
rv += nr
|
57
|
+
rescue EOFError
|
58
|
+
break
|
59
|
+
end while true
|
60
|
+
end
|
61
|
+
else
|
62
|
+
begin
|
63
|
+
r, w = IO.pipe
|
64
|
+
if len
|
65
|
+
while len > 0
|
66
|
+
nr = len > PIPE_CAPA ? PIPE_CAPA : len
|
67
|
+
nr_src = copy_stream(src, w, nr)
|
68
|
+
nr_src == nr or
|
69
|
+
raise RuntimeError, "short splice from: #{nr_src} != #{nr}"
|
70
|
+
nr_dst = copy_stream(r, dst, nr)
|
71
|
+
nr_dst == nr or
|
72
|
+
raise RuntimeError, "short splice to: #{nr_dst} != #{nr}"
|
73
|
+
len -= nr
|
74
|
+
end
|
75
|
+
else
|
76
|
+
rv = 0
|
77
|
+
begin
|
78
|
+
nr = IO.splice(src, nil, w, nil, PIPE_CAPA, F_MOVE)
|
79
|
+
rv += nr
|
80
|
+
begin
|
81
|
+
nr -= IO.splice(r, nil, dst, nil, nr, F_MOVE)
|
82
|
+
end while nr > 0
|
83
|
+
rescue EOFError
|
84
|
+
break
|
85
|
+
end while true
|
86
|
+
end
|
87
|
+
ensure
|
88
|
+
r.close
|
89
|
+
w.close
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
rv
|
94
|
+
end
|
95
|
+
|
29
96
|
end
|
30
97
|
end
|
data/test/test_io_splice.rb
CHANGED
@@ -213,6 +213,12 @@ class Test_IO_Splice < Test::Unit::TestCase
|
|
213
213
|
assert_equal data.join(''), r.readpartial(16384)
|
214
214
|
end
|
215
215
|
|
216
|
+
def test_vmsplice_string
|
217
|
+
r, w = IO.pipe
|
218
|
+
assert_equal 5, IO.vmsplice(w, 'hello', 0)
|
219
|
+
assert_equal 'hello', r.read(5)
|
220
|
+
end
|
221
|
+
|
216
222
|
def test_vmsplice_array_io
|
217
223
|
data = %w(hello world how are you today)
|
218
224
|
r, w = IO.pipe
|
@@ -272,4 +278,57 @@ class Test_IO_Splice < Test::Unit::TestCase
|
|
272
278
|
assert IO::Splice::PIPE_CAPA >= IO::Splice::PIPE_BUF
|
273
279
|
end
|
274
280
|
|
281
|
+
def test_splice_copy_stream_file_to_file_small
|
282
|
+
a, b = Tempfile.new('a'), Tempfile.new('b')
|
283
|
+
a.syswrite 'hello world'
|
284
|
+
a.sysseek(0)
|
285
|
+
IO::Splice.copy_stream(a, b)
|
286
|
+
b.rewind
|
287
|
+
assert_equal 'hello world', b.read
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_splice_copy_stream_file_to_file_big
|
291
|
+
buf = ('ab' * IO::Splice::PIPE_CAPA) + 'hi'
|
292
|
+
a, b = Tempfile.new('a'), Tempfile.new('b')
|
293
|
+
a.syswrite buf
|
294
|
+
a.sysseek(0)
|
295
|
+
IO::Splice.copy_stream(a, b)
|
296
|
+
b.rewind
|
297
|
+
assert_equal buf, b.read
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_splice_copy_stream_file_to_file_len
|
301
|
+
a, b = Tempfile.new('a'), Tempfile.new('b')
|
302
|
+
a.syswrite 'hello world'
|
303
|
+
a.sysseek(0)
|
304
|
+
IO::Splice.copy_stream(a, b, 5)
|
305
|
+
b.rewind
|
306
|
+
assert_equal 'hello', b.read
|
307
|
+
end
|
308
|
+
|
309
|
+
def test_splice_copy_stream_pipe_to_file_len
|
310
|
+
a = Tempfile.new('a')
|
311
|
+
r, w = IO.pipe
|
312
|
+
w.syswrite 'hello world'
|
313
|
+
IO::Splice.copy_stream(r, a, 5)
|
314
|
+
a.rewind
|
315
|
+
assert_equal 'hello', a.read
|
316
|
+
end
|
317
|
+
|
318
|
+
def test_splice_copy_stream_paths
|
319
|
+
a = Tempfile.new('a')
|
320
|
+
b = Tempfile.new('a')
|
321
|
+
a.syswrite('hello world')
|
322
|
+
IO::Splice.copy_stream(a.path, b.path, 5)
|
323
|
+
assert_equal 'hello', b.read
|
324
|
+
end
|
325
|
+
|
326
|
+
def test_splice_copy_stream_src_offset
|
327
|
+
a = Tempfile.new('a')
|
328
|
+
b = Tempfile.new('a')
|
329
|
+
a.syswrite('hello world')
|
330
|
+
IO::Splice.copy_stream(a.path, b.path, 5, 6)
|
331
|
+
assert_equal 'world', b.read
|
332
|
+
end
|
333
|
+
|
275
334
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: io_splice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- io_splice hackers
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-05
|
12
|
+
date: 2010-06-05 00:00:00 +00:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|