io_splice 2.2.0 → 2.2.0.18.g3025

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 CHANGED
@@ -4,3 +4,4 @@ NEWS
4
4
  ChangeLog
5
5
  lib
6
6
  ext/io_splice/io_splice_ext.c
7
+ LATEST
data/.gitignore CHANGED
@@ -13,3 +13,5 @@ Makefile
13
13
  /pkg
14
14
  /doc
15
15
  /coverage
16
+ LATEST
17
+ /tmp
data/.wrongdoc.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ cgit_url: http://bogomips.org/ruby_io_splice.git
3
+ git_url: git://bogomips.org/ruby_io_splice.git
4
+ rdoc_url: http://bogomips.org/ruby_io_splice/
5
+ changelog_start: v1.0.0
data/GNUmakefile CHANGED
@@ -1,200 +1,14 @@
1
- # use GNU Make to run tests in parallel, and without depending on RubyGems
2
1
  all::
3
- RUBY = ruby
4
- RAKE = rake
5
- GIT_URL = git://git.bogomips.org/ruby_io_splice.git
6
- RSYNC = rsync
7
2
  RCOV = rcov
8
-
9
- GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
10
- @./GIT-VERSION-GEN
11
- -include GIT-VERSION-FILE
12
- -include local.mk
13
- ifeq ($(DLEXT),) # "so" for Linux
14
- DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts Config::CONFIG["DLEXT"]')
15
- endif
16
- ifeq ($(RUBY_VERSION),)
17
- RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
18
- endif
19
-
20
- install: $(bins)
21
- $(prep_setup_rb)
22
- $(RM) -r .install-tmp
23
- mkdir .install-tmp
24
- cp -p bin/* .install-tmp
25
- $(RUBY) setup.rb all
26
- $(RM) $^
27
- mv .install-tmp/* bin/
28
- $(RM) -r .install-tmp
29
- $(prep_setup_rb)
30
-
31
- setup_rb_files := .config InstalledFiles
32
- prep_setup_rb := @-$(RM) $(setup_rb_files);$(MAKE) -C $(ext) clean
33
-
34
- clean:
35
- -$(MAKE) -C ext/io_splice clean
36
- $(RM) $(setup_rb_files) ext/io_splice/Makefile
37
-
38
- pkg_extra := GIT-VERSION-FILE NEWS ChangeLog
39
- manifest: $(pkg_extra)
40
- $(RM) .manifest
41
- $(MAKE) .manifest
42
-
43
- .manifest:
44
- (git ls-files && \
45
- for i in $@ $(pkg_extra); \
46
- do echo $$i; done) | LC_ALL=C sort > $@+
47
- cmp $@+ $@ || mv $@+ $@
48
- $(RM) $@+
49
-
50
- NEWS: GIT-VERSION-FILE
51
- $(RAKE) -s news_rdoc > $@+
52
- mv $@+ $@
53
-
54
- SINCE = 1.0.0
55
- ChangeLog: LOG_VERSION = \
56
- $(shell git rev-parse -q "$(GIT_VERSION)" >/dev/null 2>&1 && \
57
- echo $(GIT_VERSION) || git describe)
58
- ifneq ($(SINCE),)
59
- ChangeLog: log_range = v$(SINCE)..$(LOG_VERSION)
60
- endif
61
- ChangeLog: GIT-VERSION-FILE
62
- @echo "ChangeLog from $(GIT_URL) ($(log_range))" > $@+
63
- @echo >> $@+
64
- git log $(log_range) | sed -e 's/^/ /' >> $@+
65
- mv $@+ $@
66
-
67
- news_atom := http://bogomips.org/ruby_io_splice/NEWS.atom.xml
68
- cgit_atom := http://git.bogomips.org/cgit/ruby_io_splice.git/atom/?h=master
69
- atom = <link rel="alternate" title="Atom feed" href="$(1)" \
70
- type="application/atom+xml"/>
71
-
72
- # using rdoc 2.5.x + workaround patch here:
73
- # rubyforge.org/tracker/index.php?func=detail&aid=28230&group_id=627&atid=2472
74
- doc: .document NEWS ChangeLog
75
- rdoc -a -t "$(shell sed -ne '1s/^= //p' README)"
76
- install -m644 COPYING doc/COPYING
77
- install -m644 $(shell grep '^[A-Z]' .document) doc/
78
- cd doc && for i in $(base_bins); do \
79
- html=$$(echo $$i | sed 's/\.rb/_rb/')_1.html; \
80
- sed -e '/"documentation">/r man1/'$$i'.1.html' \
81
- < $$html > tmp && mv tmp $$html; done
82
- $(RUBY) -i -p -e \
83
- '$$_.gsub!("</title>",%q{\&$(call atom,$(cgit_atom))})' \
84
- doc/ChangeLog.html
85
- $(RUBY) -i -p -e \
86
- '$$_.gsub!("</title>",%q{\&$(call atom,$(news_atom))})' \
87
- doc/NEWS.html doc/README.html
88
- $(RAKE) -s news_atom > doc/NEWS.atom.xml
89
- cd doc && ln README.html tmp && mv tmp index.html
90
-
91
- latest: NEWS
92
- @awk 'BEGIN{RS="=== ";ORS=""}NR==2{sub(/\n$$/,"");print RS""$$0 }' $<
93
-
94
- # publishes docs to http://bogomips.org/ruby_io_splice/,
95
- publish_doc:
96
- -git set-file-times
97
- $(RM) -r doc ChangeLog NEWS
98
- $(MAKE) doc LOG_VERSION=$(shell git tag -l | tail -1)
99
- awk 'BEGIN{RS="=== ";ORS=""}NR==2{sub(/\n$$/,"");print RS""$$0 }' \
100
- NEWS > doc/LATEST
101
- find doc/images doc/js -type f | \
102
- TZ=UTC xargs touch -d '1970-01-01 00:00:01' doc/rdoc.css
103
- $(MAKE) doc_gz
104
- chmod 644 $$(find doc -type f)
105
- $(RSYNC) -av doc/ bogomips.org:/srv/bogomips/ruby_io_splice/
106
- git ls-files | xargs touch
107
-
108
- # Create gzip variants of the same timestamp as the original so nginx
109
- # "gzip_static on" can serve the gzipped versions directly.
110
- doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.\(gif\|jpg\|png\|gz\)$$')
111
- doc_gz:
112
- touch doc/NEWS.atom.xml -d "$$(awk 'NR==1{print $$4,$$5,$$6}' NEWS)"
113
- for i in $(docs); do \
114
- gzip --rsyncable -9 < $$i > $$i.gz; touch -r $$i $$i.gz; done
115
-
116
- ifneq ($(VERSION),)
3
+ RSYNC_DEST := bogomips.org:/srv/bogomips/ruby_io_splice
117
4
  rfproject := qrp
118
5
  rfpackage := io_splice
119
- pkggem := pkg/$(rfpackage)-$(VERSION).gem
120
- pkgtgz := pkg/$(rfpackage)-$(VERSION).tgz
121
- release_notes := release_notes-$(VERSION)
122
- release_changes := release_changes-$(VERSION)
123
-
124
- release-notes: $(release_notes)
125
- release-changes: $(release_changes)
126
- $(release_changes):
127
- $(RAKE) -s release_changes > $@+
128
- $(VISUAL) $@+ && test -s $@+ && mv $@+ $@
129
- $(release_notes):
130
- GIT_URL=$(GIT_URL) $(RAKE) -s release_notes > $@+
131
- $(VISUAL) $@+ && test -s $@+ && mv $@+ $@
132
-
133
- # ensures we're actually on the tagged $(VERSION), only used for release
134
- verify:
135
- test x"$(shell umask)" = x0022
136
- git rev-parse --verify refs/tags/v$(VERSION)^{}
137
- git diff-index --quiet HEAD^0
138
- test `git rev-parse --verify HEAD^0` = \
139
- `git rev-parse --verify refs/tags/v$(VERSION)^{}`
140
-
141
- fix-perms:
142
- -git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644
143
- -git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755
144
-
145
- gem: $(pkggem)
146
-
147
- install-gem: $(pkggem)
148
- gem install $(CURDIR)/$<
149
-
150
- $(pkggem): manifest fix-perms
151
- gem build $(rfpackage).gemspec
152
- mkdir -p pkg
153
- mv $(@F) $@
154
-
155
- $(pkgtgz): distdir = $(basename $@)
156
- $(pkgtgz): HEAD = v$(VERSION)
157
- $(pkgtgz): manifest fix-perms
158
- @test -n "$(distdir)"
159
- $(RM) -r $(distdir)
160
- mkdir -p $(distdir)
161
- tar c `cat .manifest` | (cd $(distdir) && tar x)
162
- cd pkg && tar c $(basename $(@F)) | gzip -9 > $(@F)+
163
- mv $@+ $@
164
-
165
- package: $(pkgtgz) $(pkggem)
166
-
167
- test-release: verify package $(release_notes) $(release_changes)
168
- release: verify package $(release_notes) $(release_changes)
169
- # make tgz release on RubyForge
170
- rubyforge add_release -f -n $(release_notes) -a $(release_changes) \
171
- $(rfproject) $(rfpackage) $(VERSION) $(pkgtgz)
172
- # push gem to Gemcutter
173
- gem push $(pkggem)
174
- # in case of gem downloads from RubyForge releases page
175
- -rubyforge add_file \
176
- $(rfproject) $(rfpackage) $(VERSION) $(pkggem)
177
- else
178
- gem install-gem: GIT-VERSION-FILE
179
- $(MAKE) $@ VERSION=$(GIT_VERSION)
6
+ include pkg.mk
7
+ ifneq ($(VERSION),)
8
+ release::
9
+ $(RAKE) raa_update VERSION=$(VERSION)
10
+ $(RAKE) publish_news VERSION=$(VERSION)
180
11
  endif
181
-
182
- ext := ext/io_splice/io_splice_ext.$(DLEXT)
183
- ext/io_splice/Makefile: ext/io_splice/extconf.rb
184
- cd $(@D) && $(RUBY) extconf.rb
185
- $(ext): $(wildcard $(addprefix ext/io_splice/,*.c *.h)) ext/io_splice/Makefile
186
- $(MAKE) -C $(@D)
187
-
188
- all:: test
189
-
190
- build: $(ext)
191
- test: test-unit
192
12
  rcov: build
193
13
  $(RCOV) -I lib:ext/io_splice $(test_unit)
194
-
195
- test_unit := $(wildcard test/test_*.rb)
196
- $(test_unit): build
197
- $(RUBY) -I lib:ext/io_splice $@ $(TEST_UNIT_OPTS)
198
- test-unit: $(test_unit)
199
-
200
- .PHONY: .FORCE-GIT-VERSION-FILE doc manifest man test $(test_unit)
14
+ .PHONY: rcov
data/README CHANGED
@@ -66,13 +66,13 @@ standardized and available in non-Linux kernels some day.
66
66
 
67
67
  You can get the latest source via git from the following locations:
68
68
 
69
- git://git.bogomips.org/ruby_io_splice.git
69
+ git://bogomips.org/ruby_io_splice.git
70
70
  git://repo.or.cz/ruby_io_splice.git (mirror)
71
71
 
72
72
  You may browse the code from the web and download the latest snapshot
73
73
  tarballs here:
74
74
 
75
- * http://git.bogomips.org/cgit/ruby_io_splice.git (cgit)
75
+ * http://bogomips.org/cgit/ruby_io_splice.git (cgit)
76
76
  * http://repo.or.cz/w/ruby_io_splice.git (gitweb)
77
77
 
78
78
  Inline patches (from "git format-patch") to the mailing list are
data/Rakefile CHANGED
@@ -1,112 +1,26 @@
1
1
  # -*- encoding: binary -*-
2
+ require 'wrongdoc'
3
+ cgit_url = Wrongdoc.config[:cgit_url]
4
+ git_url = Wrongdoc.config[:git_url]
2
5
 
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"
6
+ desc "post news article to rubyforge"
106
7
  task :publish_news do
107
8
  require 'rubyforge'
108
- IO.select([STDIN], nil, nil, 1) or abort "E: news must be read from stdin"
109
- msg = STDIN.readlines
9
+ spec = Gem::Specification.load('io_splice.gemspec')
10
+ tmp = Tempfile.new('rf-news')
11
+ _, subject, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
12
+ tmp.puts subject
13
+ tmp.puts
14
+ tmp.puts spec.description.strip
15
+ tmp.puts ""
16
+ tmp.puts "* #{spec.homepage}"
17
+ tmp.puts "* #{spec.email}"
18
+ tmp.puts "* #{git_url}"
19
+ tmp.print "\nChanges:\n\n"
20
+ tmp.puts body
21
+ tmp.flush
22
+ system(ENV["VISUAL"], tmp.path) or abort "#{ENV["VISUAL"]} failed: #$?"
23
+ msg = File.readlines(tmp.path)
110
24
  subject = msg.shift
111
25
  blank = msg.shift
112
26
  blank == "\n" or abort "no newline after subject!"
@@ -144,7 +58,7 @@ task :raa_update do
144
58
  :category_minor => 'System',
145
59
  :url => s.homepage,
146
60
  :download => 'http://rubyforge.org/frs/?group_id=5626',
147
- :license => 'LGPL',
61
+ :license => "LGPL",
148
62
  :description_style => 'Plain',
149
63
  :description => desc,
150
64
  :pass => password,
@@ -12,6 +12,8 @@
12
12
  #include <alloca.h>
13
13
  #include <sys/utsname.h>
14
14
 
15
+ static VALUE sym_EAGAIN;
16
+
15
17
  #ifndef F_LINUX_SPECIFIC_BASE
16
18
  # define F_LINUX_SPECIFIC_BASE 1024
17
19
  #endif
@@ -88,8 +90,14 @@ rb_thread_blocking_region(
88
90
  # define RARRAY_LEN(s) (RARRAY(s)->len)
89
91
  #endif
90
92
 
93
+ static VALUE io_run(rb_blocking_function_t *fn, void *data)
94
+ {
95
+ return rb_thread_blocking_region(fn, data, RUBY_UBF_IO, 0);
96
+ }
97
+
91
98
  /*
92
99
  * Releases GVL only iff blocking I/O is used.
100
+ * Only use this if all file descriptors in data are pipes.
93
101
  * We'll trust programmers who use non-blocking I/O explicitly to
94
102
  * want the fastest possible performance without resorting to threads,
95
103
  * so releasing and them immediately reacquiring the GVL would be
@@ -99,7 +107,7 @@ static VALUE nb_io_run(rb_blocking_function_t *fn, void *data, unsigned flags)
99
107
  {
100
108
  if (flags & SPLICE_F_NONBLOCK)
101
109
  return fn(data);
102
- return rb_thread_blocking_region(fn, data, RUBY_UBF_IO, 0);
110
+ return io_run(fn, data);
103
111
  }
104
112
 
105
113
  struct splice_args {
@@ -119,8 +127,28 @@ static VALUE nogvl_splice(void *ptr)
119
127
  a->len, a->flags);
120
128
  }
121
129
 
130
+ static long do_splice(int argc, VALUE *argv, unsigned dflags)
131
+ {
132
+ off_t i, o;
133
+ VALUE fd_in, off_in, fd_out, off_out, len, flags;
134
+ struct splice_args a;
135
+
136
+ rb_scan_args(argc, argv, "51",
137
+ &fd_in, &off_in, &fd_out, &off_out, &len, &flags);
138
+
139
+ a.off_in = NIL_P(off_in) ? NULL : (i = NUM2OFFT(off_in), &i);
140
+ a.off_out = NIL_P(off_out) ? NULL : (o = NUM2OFFT(off_out), &o);
141
+ a.fd_in = my_fileno(fd_in);
142
+ a.fd_out = my_fileno(fd_out);
143
+ a.len = (size_t)NUM2ULONG(len);
144
+ a.flags = NIL_P(flags) ? dflags : NUM2UINT(flags) | dflags;
145
+
146
+ return (long)io_run(nogvl_splice, &a);
147
+ }
148
+
122
149
  /*
123
150
  * call-seq:
151
+ * IO.splice(fd_in, off_in, fd_out, off_out, len) => integer
124
152
  * IO.splice(fd_in, off_in, fd_out, off_out, len, flags) => integer
125
153
  *
126
154
  * Splice +len+ bytes from/to a pipe. Either +fd_in+ or +fd_out+
@@ -130,6 +158,7 @@ static VALUE nogvl_splice(void *ptr)
130
158
  * +off_in+ and +off_out+ if non-nil may be used to
131
159
  * specify an offset for the non-pipe file descriptor.
132
160
  *
161
+ * +flags+ defaults to zero if unspecified.
133
162
  * +flags+ may be a bitmask of the following flags:
134
163
  *
135
164
  * IO::Splice::F_MOVE, IO::Splice::F_NONBLOCK, IO::Splice::F_MORE
@@ -152,26 +181,17 @@ static VALUE nogvl_splice(void *ptr)
152
181
  * into account userspace buffering done by Ruby or stdio. It is
153
182
  * also not subject to encoding/decoding filters under Ruby 1.9.
154
183
  *
184
+ * Consider using IO.trysplice if you are using non-blocking I/O on
185
+ * both descriptors as it avoids the cost of raising common Errno::EAGAIN
186
+ * exceptions.
187
+ *
155
188
  * See manpage for full documentation:
156
189
  * http://kernel.org/doc/man-pages/online/pages/man2/splice.2.html
157
190
  */
158
- static VALUE my_splice(VALUE self,
159
- VALUE fd_in, VALUE off_in,
160
- VALUE fd_out, VALUE off_out,
161
- VALUE len, VALUE flags)
191
+ static VALUE my_splice(int argc, VALUE *argv, VALUE self)
162
192
  {
163
- off_t i, o;
164
- long n;
165
- struct splice_args a = {
166
- .off_in = NIL_P(off_in) ? NULL : (i = NUM2OFFT(off_in), &i),
167
- .off_out = NIL_P(off_out) ? NULL : (o = NUM2OFFT(off_out), &o),
168
- .fd_in = my_fileno(fd_in),
169
- .fd_out = my_fileno(fd_out),
170
- .len = (size_t)NUM2ULONG(len),
171
- .flags = NUM2UINT(flags),
172
- };
173
-
174
- n = (long)rb_thread_blocking_region(nogvl_splice, &a, RUBY_UBF_IO, 0);
193
+ long n = do_splice(argc, argv, 0);
194
+
175
195
  if (n == 0)
176
196
  rb_eof_error();
177
197
  if (n < 0)
@@ -179,6 +199,33 @@ static VALUE my_splice(VALUE self,
179
199
  return LONG2NUM(n);
180
200
  }
181
201
 
202
+ /*
203
+ * call-seq:
204
+ * IO.trysplice(fd_in, off_in, fd_out, off_out, len) => integer
205
+ * IO.trysplice(fd_in, off_in, fd_out, off_out, len, flags) => integer
206
+ *
207
+ * Exactly like IO.splice, except +:EAGAIN+ is returned when either
208
+ * the read or write end would block instead of raising Errno::EAGAIN.
209
+ *
210
+ * IO::Splice::F_NONBLOCK is always passed for the pipe descriptor,
211
+ * but this can still block if the non-pipe descriptor is blocking.
212
+ *
213
+ * See IO.splice documentation for more details.
214
+ */
215
+ static VALUE trysplice(int argc, VALUE *argv, VALUE self)
216
+ {
217
+ long n = do_splice(argc, argv, SPLICE_F_NONBLOCK);
218
+
219
+ if (n == 0)
220
+ return Qnil;
221
+ if (n < 0) {
222
+ if (errno == EAGAIN)
223
+ return sym_EAGAIN;
224
+ rb_sys_fail("splice");
225
+ }
226
+ return LONG2NUM(n);
227
+ }
228
+
182
229
  struct tee_args {
183
230
  int fd_in;
184
231
  int fd_out;
@@ -194,15 +241,30 @@ static VALUE nogvl_tee(void *ptr)
194
241
  return (VALUE)tee(a->fd_in, a->fd_out, a->len, a->flags);
195
242
  }
196
243
 
244
+ static long do_tee(int argc, VALUE *argv, unsigned dflags)
245
+ {
246
+ VALUE fd_in, fd_out, len, flags;
247
+ struct tee_args a;
248
+
249
+ rb_scan_args(argc, argv, "31", &fd_in, &fd_out, &len, &flags);
250
+ a.fd_in = my_fileno(fd_in);
251
+ a.fd_out = my_fileno(fd_out);
252
+ a.len = (size_t)NUM2ULONG(len);
253
+ a.flags = NIL_P(flags) ? dflags : NUM2UINT(flags) | dflags;
254
+
255
+ return (long)nb_io_run(nogvl_tee, &a, a.flags);
256
+ }
257
+
197
258
  /*
198
259
  * call-seq:
260
+ * IO.tee(fd_in, fd_out, len) => integer
199
261
  * IO.tee(fd_in, fd_out, len, flags) => integer
200
262
  *
201
263
  * Copies up to +len+ bytes of data from +fd_in+ to +fd_out+. +fd_in+
202
264
  * and +fd_out+ must both refer to pipe descriptors. +fd_in+ and +fd_out+
203
265
  * may not be endpoints of the same pipe.
204
266
  *
205
- * +flags+ may be zero or IO::Splice::F_NONBLOCK
267
+ * +flags+ may be zero (the default) or IO::Splice::F_NONBLOCK
206
268
  * Other IO::Splice flags are currently unimplemented or have no effect.
207
269
  *
208
270
  * Returns the number of bytes duplicated if successful.
@@ -210,22 +272,16 @@ static VALUE nogvl_tee(void *ptr)
210
272
  * Raises Errno::EAGAIN when +fd_in+ is empty and/or +fd_out+ is full
211
273
  * and +flags+ contains IO::Splice::F_NONBLOCK
212
274
  *
275
+ * Consider using IO.trytee if you are using IO::Splice::F_NONBLOCK
276
+ * as it avoids the cost of raising common Errno::EAGAIN exceptions.
277
+ *
213
278
  * See manpage for full documentation:
214
279
  * http://kernel.org/doc/man-pages/online/pages/man2/tee.2.html
215
280
  */
216
- static VALUE my_tee(VALUE self,
217
- VALUE fd_in, VALUE fd_out,
218
- VALUE len, VALUE flags)
281
+ static VALUE my_tee(int argc, VALUE *argv, VALUE self)
219
282
  {
220
- long n;
221
- struct tee_args a = {
222
- .fd_in = my_fileno(fd_in),
223
- .fd_out = my_fileno(fd_out),
224
- .len = (size_t)NUM2ULONG(len),
225
- .flags = NUM2UINT(flags),
226
- };
227
-
228
- n = (long)nb_io_run(nogvl_tee, &a, a.flags);
283
+ long n = do_tee(argc, argv, 0);
284
+
229
285
  if (n == 0)
230
286
  rb_eof_error();
231
287
  if (n < 0)
@@ -234,6 +290,34 @@ static VALUE my_tee(VALUE self,
234
290
  return LONG2NUM(n);
235
291
  }
236
292
 
293
+ /*
294
+ * call-seq:
295
+ * IO.trytee(fd_in, fd_out, len) => integer
296
+ * IO.trytee(fd_in, fd_out, len, flags) => integer
297
+ *
298
+ * Exactly like IO.tee, except +:EAGAIN+ is returned when either
299
+ * the read or write end would block instead of raising Errno::EAGAIN.
300
+ *
301
+ * IO::Splice::F_NONBLOCK is always passed for the pipe descriptor,
302
+ * but this can still block if the non-pipe descriptor is blocking.
303
+ *
304
+ * See IO.tee documentation for more details.
305
+ */
306
+ static VALUE trytee(int argc, VALUE *argv, VALUE self)
307
+ {
308
+ long n = do_tee(argc, argv, SPLICE_F_NONBLOCK);
309
+
310
+ if (n == 0)
311
+ return Qnil;
312
+ if (n < 0) {
313
+ if (errno == EAGAIN)
314
+ return sym_EAGAIN;
315
+ rb_sys_fail("tee");
316
+ }
317
+
318
+ return LONG2NUM(n);
319
+ }
320
+
237
321
  struct vmsplice_args {
238
322
  int fd;
239
323
  struct iovec *iov;
@@ -272,7 +356,7 @@ do { \
272
356
  static void advance_vmsplice_args(struct vmsplice_args *a, long n)
273
357
  {
274
358
  struct iovec *new_iov = a->iov;
275
- int i;
359
+ unsigned long i;
276
360
 
277
361
  /* skip over iovecs we've already written completely */
278
362
  for (i = 0; i < a->nr_segs; i++, new_iov++) {
@@ -301,7 +385,9 @@ static void advance_vmsplice_args(struct vmsplice_args *a, long n)
301
385
 
302
386
  /*
303
387
  * call-seq:
388
+ * IO.vmsplice(fd, string_array) => integer
304
389
  * IO.vmsplice(fd, string_array, flags) => integer
390
+ * IO.vmsplice(fd, string) => integer
305
391
  * IO.vmsplice(fd, string, flags) => integer
306
392
  *
307
393
  * Transfers an array of strings into the pipe descriptor given by fd.
@@ -313,11 +399,14 @@ static void advance_vmsplice_args(struct vmsplice_args *a, long n)
313
399
  * See manpage for full documentation:
314
400
  * http://kernel.org/doc/man-pages/online/pages/man2/vmsplice.2.html
315
401
  */
316
- static VALUE my_vmsplice(VALUE self, VALUE fd, VALUE data, VALUE flags)
402
+ static VALUE my_vmsplice(int argc, VALUE * argv, VALUE self)
317
403
  {
318
404
  long rv = 0;
319
405
  ssize_t left;
320
406
  struct vmsplice_args a;
407
+ VALUE fd, data, flags;
408
+
409
+ rb_scan_args(argc, argv, "21", &fd, &data, &flags);
321
410
 
322
411
  switch (TYPE(data)) {
323
412
  case T_STRING: {
@@ -338,7 +427,7 @@ static VALUE my_vmsplice(VALUE self, VALUE fd, VALUE data, VALUE flags)
338
427
  rb_obj_classname(data));
339
428
  }
340
429
  a.fd = my_fileno(fd);
341
- a.flags = NUM2UINT(flags);
430
+ a.flags = NIL_P(flags) ? 0 : NUM2UINT(flags);
342
431
 
343
432
  for (;;) {
344
433
  long n = (long)nb_io_run(nogvl_vmsplice, &a, a.flags);
@@ -442,9 +531,11 @@ void Init_io_splice_ext(void)
442
531
  VALUE mSplice = rb_define_module_under(rb_cIO, "Splice");
443
532
  struct utsname utsname;
444
533
 
445
- rb_define_singleton_method(rb_cIO, "splice", my_splice, 6);
446
- rb_define_singleton_method(rb_cIO, "tee", my_tee, 4);
447
- rb_define_singleton_method(rb_cIO, "vmsplice", my_vmsplice, 3);
534
+ rb_define_singleton_method(rb_cIO, "splice", my_splice, -1);
535
+ rb_define_singleton_method(rb_cIO, "trysplice", trysplice, -1);
536
+ rb_define_singleton_method(rb_cIO, "tee", my_tee, -1);
537
+ rb_define_singleton_method(rb_cIO, "trytee", trytee, -1);
538
+ rb_define_singleton_method(rb_cIO, "vmsplice", my_vmsplice, -1);
448
539
 
449
540
  /*
450
541
  * Attempt to move pages instead of copying. This is only a hint
@@ -510,4 +601,6 @@ void Init_io_splice_ext(void)
510
601
  rb_define_const(mSplice, "F_SETPIPE_SZ",
511
602
  UINT2NUM(F_SETPIPE_SZ));
512
603
  }
604
+
605
+ sym_EAGAIN = ID2SYM(rb_intern("EAGAIN"));
513
606
  }