Ruby io_splice 2.1.0 - IO::Splice.copy_stream fixes
[ruby_io_splice.git] / lib / io / splice.rb
blobd60a24d9075c9a98d80dee76e0782ceca980dc96
1 # -*- encoding: binary -*-
2 require 'io_splice_ext'
4 class IO
6   module Splice
8     # the version of IO::Splice, currently 2.1.0
9     VERSION = '2.1.0'
11     # The maximum default capacity of the pipe in bytes.
12     # Under stock Linux, this is 65536 bytes as of 2.6.11, and 4096 before
13     # We detect this at runtime as it is easy to recompile the kernel
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.
18     PIPE_CAPA = begin
19       rd, wr = IO.pipe
20       buf = ' ' * PIPE_BUF
21       n = 0
22       begin
23         n += wr.write_nonblock(buf)
24       rescue Errno::EAGAIN
25         break
26       end while true
27       wr.close
28       rd.close
29       n
30     end
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 Splice.copy_stream(src, dst, len = nil, src_offset = nil)
37       close = []
38       src.kind_of?(String) and close << (src = File.open(src, 'rb'))
39       dst.kind_of?(String) and close << (dst = File.open(dst, 'wb'))
40       src, dst = src.to_io, dst.to_io
41       rv = len
42       src.sysseek(src_offset) if src_offset
43       select_args = selectable(src, dst)
45       if src.stat.pipe? || dst.stat.pipe?
46         if len
47           len -= full(src, dst, len, select_args) until len == 0
48         else
49           rv = 0
50           begin
51             rv += partial(src, dst, PIPE_CAPA, select_args)
52           rescue EOFError
53             break
54           end while true
55         end
56       else
57         r, w = tmp = IO.pipe
58         close.concat(tmp)
59         if len
60           while len != 0
61             nr = partial(src, w, len, select_args)
62             len -= full(r, dst, nr, select_args)
63           end
64         else
65           rv = 0
66           begin
67             nr = partial(src, w, PIPE_CAPA, select_args)
68             rv += full(r, dst, nr, select_args)
69           rescue EOFError
70             break
71           end while true
72         end
73       end
75       rv
76       ensure
77         close.each { |io| io.close }
78     end
80     # splice the full amount specified from +src+ to +dst+
81     # Either +dst+ or +src+ must be a pipe.  +dst+ and +src+
82     # may BOTH be pipes in Linux 2.6.31 or later.
83     # This will block and wait for IO completion of +len+
84     # bytes.  Returns the nubmer of bytes actually spliced (always +len+)
85     # The +_select_args+ parameter is reserved for internal use and
86     # may be removed in future versions.  Do not write code that
87     # depends on +_select_args+.
88     def Splice.full(src, dst, len, _select_args = selectable(src, dst))
89       nr = len
90       nr -= partial(src, dst, nr, _select_args) until nr == 0
91       len
92     end
94     # splice up to +len+ bytes from +src+ to +dst+.
95     # Either +dst+ or +src+ must be a pipe.  +dst+ and +src+
96     # may BOTH be pipes in Linux 2.6.31 or later.
97     # Returns the number of bytes actually spliced.
98     # Like IO#readpartial, this never returns Errno::EAGAIN
99     # The +_select_args+ parameter is reserved for internal use and
100     # may be removed in future versions.  Do not write code that
101     # depends on +_select_args+.
102     def Splice.partial(src, dst, len, _select_args = selectable(src, dst))
103       begin
104         IO.splice(src, nil, dst, nil, len, F_MOVE)
105       rescue Errno::EAGAIN
106         IO.select(*_select_args)
107         retry
108       end
109     end
111     # returns an array suitable for splat-ing to IO.select for blocking I/O
112     def Splice.selectable(src, dst)
113       rv = []
114       src.stat.pipe? or rv[0] = [ src ]
115       dst.stat.pipe? or rv[1] = [ dst ]
116       rv
117     end
119   end