limit maximum splice length to 1 << 30
[ruby_io_splice.git] / lib / io / splice.rb
blob65d8ed439bffdaf14664ca690373045697dc5614
1 # -*- encoding: binary -*-
2 require 'io_splice_ext'
3 require 'io/wait'
5 module IO::Splice
7   # The maximum default capacity of the pipe in bytes.
8   # Under stock Linux, this is 65536 bytes as of 2.6.11, and 4096 before
9   # We detect this at runtime as it is easy to recompile the kernel
10   # and set a new value.
11   # Starting with Linux 2.6.35, pipe capacity will be tunable
12   # and this will only represent the default capacity of a
13   # newly-created pipe.
14   PIPE_CAPA = begin
15     rd, wr = IO.pipe
16     buf = ' ' * PIPE_BUF
17     n = 0
18     begin
19       n += wr.write_nonblock(buf)
20     rescue Errno::EAGAIN
21       break
22     end while true
23     wr.close
24     rd.close
25     n
26   end
28   def self.need_open?(obj) # :nodoc:
29     return false if obj.respond_to?(:to_io)
30     obj.respond_to?(:to_path) || obj.kind_of?(String)
31   end
33   # copies the contents of the IO object given by +src+ to +dst+
34   # If +len+ is specified, then only +len+ bytes are copied and
35   # +EOFError+ is raised if fewer than +len+ bytes could be copied.
36   # Otherwise the copy will be until EOF is reached on the +src+.
37   # +src+ and +dst+ must be IO objects or respond to +to_io+
38   #
39   # This is nearly a drop-in replacement for IO.copy_stream (in Ruby 1.9)
40   # but does not take into account userspace I/O buffers nor IO-like
41   # objects with no underlying file descriptor (e.g. StringIO).
42   def self.copy_stream(src, dst, len = nil, src_offset = nil)
43     close = []
44     need_open?(src) and close << (src = File.open(src))
45     need_open?(dst) and close << (dst = File.open(dst, "w"))
46     rv = len
47     src, dst = src.to_io, dst.to_io
49     if src.stat.pipe? || dst.stat.pipe?
50       return full(src, dst, len, src_offset) if len
51       rv = 0
52       while n = partial(src, dst, MAX_AT_ONCE, src_offset)
53         rv += n
54         src_offset += n if src_offset
55       end
56     else
57       r, w = tmp = IO.pipe
58       close.concat(tmp)
59       rv = 0
60       if len
61         while len != 0 && n = partial(src, w, len, src_offset)
62           src_offset += n if src_offset
63           rv += n
64           len -= full(r, dst, n, nil)
65         end
66       else
67         while n = partial(src, w, MAX_AT_ONCE, src_offset)
68           src_offset += n if src_offset
69           rv += full(r, dst, n, nil)
70         end
71       end
72     end
74     rv
75     ensure
76       close.each { |io| io.close }
77   end
79   # splice the full amount specified from +src+ to +dst+
80   # Either +dst+ or +src+ must be a pipe.  +dst+ and +src+
81   # may BOTH be pipes in Linux 2.6.31 or later.
82   # This will block and wait for IO completion of +len+
83   # Raises +EOFError+ if end of file is reached.
84   # bytes.  Returns the number of bytes actually spliced (always +len+)
85   # unless +src+ does not have +len+ bytes to read.
86   #
87   # Do not use this method to splice a socket +src+ into a pipe +dst+
88   # unless there is another process or native thread doing a blocking
89   # read on the other end of the +dst+ pipe.
90   #
91   # This method is safe for splicing a pipe +src+ into any type of +dst+ IO.
92   def self.full(src, dst, len, src_offset)
93     IO.splice(src, src_offset, dst, nil, len, F_MOVE | WAITALL)
94   end
96   # splice up to +len+ bytes from +src+ to +dst+.
97   # Either +dst+ or +src+ must be a pipe.  +dst+ and +src+
98   # may BOTH be pipes in Linux 2.6.31 or later.
99   # Returns the number of bytes actually spliced.
100   # Like IO#readpartial, this never returns Errno::EAGAIN
101   def self.partial(src, dst, len, src_offset)
102     case rv = IO.trysplice(src, src_offset, dst, nil, len, F_MOVE)
103     when :EAGAIN
104       src.to_io.wait
105       IO.select(nil, [dst])
106     when Integer
107       return rv
108     else
109       return nil
110     end while true
111   end
113 if (! defined?(RUBY_ENGINE) || RUBY_ENGINE == "ruby") &&
114    RUBY_VERSION.to_f <= 1.8
115   require "io/splice/mri_18"