io_splice 1.0.0 → 2.0.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/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.1.0.GIT
4
+ DEF_VER=v2.0.0.GIT
5
5
 
6
6
  LF='
7
7
  '
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
- .PHONY: .FORCE-GIT-VERSION-FILE doc manifest man test
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 3.0 {LGPLv3}[http://www.gnu.org/licenses/lgpl-3.0.txt] (see
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 => 'experimental',
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', # LGPLv3, actually, but RAA is ancient...
147
+ :license => 'LGPL',
148
148
  :description_style => 'Plain',
149
149
  :description => desc,
150
150
  :pass => password,
@@ -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)
@@ -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(stdin_fd, stdout_fd, IO::Splice::PIPE_CAPA, 0)
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(stdin_fd, nil, out_fd, nil, nread, 0)
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}"
@@ -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
- struct iovec *iov;
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
- ARY2IOVEC(a.iov, a.nr_segs, left, data);
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 and has not been
357
- * readded as of 2.6.30.
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
@@ -34,5 +34,5 @@ Gem::Specification.new do |s|
34
34
 
35
35
  s.test_files = test_files
36
36
 
37
- # s.licenses = %w(LGPLv3) # accessor not compatible with older RubyGems
37
+ # s.licenses = %w(LGPL) # accessor not compatible with older RubyGems
38
38
  end
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.1.0
9
- VERSION = '1.0.0'
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
@@ -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: 1.0.0
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-27 00:00:00 +00:00
12
+ date: 2010-06-05 00:00:00 +00:00
13
13
  default_executable:
14
14
  dependencies: []
15
15