kgio-2.11.2/0000755000004100000410000000000013241032461012552 5ustar www-datawww-datakgio-2.11.2/kgio.gemspec0000644000004100000410000000145413241032461015054 0ustar www-datawww-dataENV["VERSION"] or abort "VERSION= must be specified" manifest = File.readlines('.manifest').map! { |x| x.chomp! } require 'olddoc' extend Olddoc::Gemspec name, summary, title = readme_metadata Gem::Specification.new do |s| s.name = %q{kgio} s.version = ENV["VERSION"].dup s.homepage = Olddoc.config['rdoc_url'] s.authors = ["#{name} hackers"] s.description = readme_description s.email = %q{kgio-public@bogomips.org} s.extra_rdoc_files = extra_rdoc_files(manifest) s.files = manifest s.summary = summary s.test_files = Dir['test/test_*.rb'] s.extensions = %w(ext/kgio/extconf.rb) s.add_development_dependency('olddoc', '~> 1.0') s.add_development_dependency('test-unit', '~> 3.0') # s.add_development_dependency('strace_me', '~> 1.0') # Linux only s.licenses = %w(LGPL-2.1+) end kgio-2.11.2/ISSUES0000644000004100000410000000225013241032461013507 0ustar www-datawww-data= Issues The kgio {mailing list}[mailto:kgio-public@bogomips.org] is the best place to report bugs, submit patches and/or obtain support after you have searched the mailing list archives at https://bogomips.org/kgio-public/ and {documentation}[https://bogomips.org/kgio/]. * Do not {top post}[http://catb.org/jargon/html/T/top-post.html] in replies * Quote only the relevant portions of the message you're replying to * Do not send any HTML mail at all If your issue is of a sensitive nature or you're just shy in public, then feel free to email us privately at mailto:kgio@bogomips.org instead and your issue will be handled discreetly. If you don't get a response within a few days, we may have forgotten about it so feel free to ask again. == Submitting Patches See the HACKING document (and additionally, the Documentation/SubmittingPatches document distributed with git) on guidelines for patch submission. == Mailing List Info * subscribe: mailto:kgio-public+subscribe@bogomips.org * post: mailto:kgio-public@bogomips.org * private: mailto:kgio@bogomips.org == Mailing List Archives * https://bogomips.org/kgio-public/ * nntp://news.public-inbox.org/inbox.comp.lang.ruby.kgio kgio-2.11.2/.olddoc.yml0000644000004100000410000000060213241032461014615 0ustar www-datawww-data--- cgit_url: https://bogomips.org/kgio.git git_url: git://bogomips.org/kgio.git ml_url: https://bogomips.org/kgio-public/ rdoc_url: https://bogomips.org/kgio/ public_email: kgio-public@bogomips.org private_email: kgio@bogomips.org noindex: - LATEST - TODO nntp_url: - nntp://news.public-inbox.org/inbox.comp.lang.ruby.kgio - nntp://news.gmane.org/gmane.comp.lang.ruby.kgio.general kgio-2.11.2/LATEST0000644000004100000410000000053213241032461013471 0ustar www-datawww-data=== kgio 2.11.2 - fix Ruby 2.5 compatibility for accept_class / 2018-01-30 21:11 UTC This release fixes a bug affecting the Rainbows! web server which uses Kgio.accept_class: https://bogomips.org/rainbows-public/CANp6QoJXRbPLqmOPpG7XmZBc+oYqLWCcq1ipvOyNEQPXDEwx9Q@mail.gmail.com/ * accept: avoid passing unnecessary arg to rb_funcall kgio-2.11.2/GNUmakefile0000644000004100000410000000012513241032461014622 0ustar www-datawww-dataall:: RSYNC_DEST := bogomips.org:/srv/bogomips/kgio rfpackage := kgio include pkg.mk kgio-2.11.2/README0000644000004100000410000000406313241032461013435 0ustar www-datawww-data= kgio - kinder, gentler I/O for Ruby This is a legacy project, do not use it for new projects. Ruby 2.3 and later should make this obsolete. kgio provides non-blocking I/O methods for Ruby without raising exceptions on EAGAIN and EINPROGRESS. == Features * Can avoid expensive exceptions on common EAGAIN/EINPROGRESS errors, returning :wait_readable or :wait_writable instead. These exceptions got more expensive to hit under Ruby 1.9.2 (but were fixed in Ruby 1.9.3 and later to 1.9.1 performance levels, which were still bad) * Returns the unwritten portion of the string on partial writes, making it ideal for buffering unwritten data. * May call any method defined to be "kgio_wait_writable" or "kgio_wait_readable" methods to allow socket/pipe objects to make custom callbacks (such as adding the file descriptor to a poll set and yielding the current Fiber). * Uses {accept4}[http://man7.org/linux/man-pages/man2/accept4.2.html] on newer GNU/Linux systems to avoid unnecessary fcntl() calls * Uses MSG_DONTWAIT on GNU/Linux to further avoid unnecessary fcntl() calls * Compatible with existing Ruby IO objects and Ruby threading. == Install The library consists of a C extension so you'll need a Unix-like system with a C compiler and Ruby development libraries/headers. You may install it via RubyGems.org: gem install kgio You can get the latest source via git from the following locations (these versions may not be stable): git://bogomips.org/kgio.git git://repo.or.cz/kgio.git (mirror) You may browse the code from the web and download the latest snapshot tarballs here: * https://bogomips.org/kgio.git * http://repo.or.cz/w/kgio.git (gitweb) See the HACKING guide on how to contribute and build prerelease gems from git. == Contact All feedback (bug reports, user/development dicussion, patches, pull requests) go to the mailing list/newsgroup. See the ISSUES document for information on the {kgio mailing list}[mailto:kgio-public@bogomips.org] For the latest on kgio releases, you may check our NEWS page (and subscribe to our Atom feed). kgio-2.11.2/NEWS0000644000004100000410000003625713241032461013266 0ustar www-datawww-data=== kgio 2.11.2 - fix Ruby 2.5 compatibility for accept_class / 2018-01-30 21:11 UTC This release fixes a bug affecting the Rainbows! web server which uses Kgio.accept_class: https://bogomips.org/rainbows-public/CANp6QoJXRbPLqmOPpG7XmZBc+oYqLWCcq1ipvOyNEQPXDEwx9Q@mail.gmail.com/ * accept: avoid passing unnecessary arg to rb_funcall === kgio 2.11.1 - fix Ruby 2.5.0dev compatibility / 2017-12-15 19:40 UTC This release fixes compatibility with Ruby 2.5.0 preview and release candidates (and thus should be ready for 2.5.0 final) on some platforms. At least clang 5.0.0 on OpenBSD is affected. Thanks to Jeremy Evans for the fix. There's also minor documentation updates since 2.11.0 === kgio 2.11.0 - reverting 2.10.0 breakage / 2016-12-16 22:56 UTC Mainly this release reverts back to kgio 2.9.3 behavior by supporting "autopush" functionality in the C extension. Removing this feature in 2.10.0 was a monumental mistake(*) on my part which caused a performance regression for ONE known user of kgio on the deprecated (but supported) Rainbows! server. This release does not affect known users outside of Rainbows!, but we may have unknown users, too. The attempt at a pure Ruby version of autopush had flaws and regressions in real-world testing, so that was abandoned. So yeah, kgio will be supported indefinitely even in the face of internal changes to MRI, but I strongly discourage its use for new projects. Users who only support Ruby 2.3 (and later) are encouraged to use `exception: false` with all *_nonblock methods. 4 changes since 2.10.0: resurrect Kgio.autopush support in pure Ruby [REVERTED] reinstate the original (and dangerous) autopush in C TODO: update with Ruby 2.3 status test: increase test data sizes to fill socket buffers (*) - as was making this a separate project, at all === kgio 2.10.0 - slimming down... / 2015-09-06 08:12 UTC The largely-unused autopush functionality is now a no-op. Existing applications will probably not notice, and are better off using MSG_MORE anyways if they're on Linux. Our poll(2) wrapper now uses rb_hash_clear if available, reducing global method cache dependence. shortlog of changes since 2.9.3: README: remove reference to Rainbows! writev: update comment on preserving errno poll: avoid rb_funcall for rb_hash_clear minor doc updates remove autopush support and make it a no-op HACKING: update URL for Message-ID lookup === kgio 2.9.3 - minor cleanups and fixes / 2015-01-12 08:46 UTC Most notably there's a new RB_GC_GUARD to fix an occasional segfault at load time for Ruby 1.8 users thanks to Petr Novodvorskiy for reporting the issue. There's been a new mailing list for a few months now at: kgio-public@bogomips.org No subscription is required, but if you wish to subscribe (existing librelist subscribers must resubscribe) kgio-public+subscribe@bogomips.org There's also a bunch of code cleanups and packaging cleanups. Our website is faster now thanks to olddoc: http://bogomips.org/kgio/ shortlog: test/lib_read_write: fix trywritev blocking test new mailing list info: kgio-public@bogomips.org relax license to allow LGPLv2.1 or later gemspec: modernize for more recent rubygems doc: switch to olddoc remove references to rubyforge and ancient Rubies TODO: updates for the future of kgio tryopen: add RB_GC_GUARD for Ruby 1.8 LICENSE: prefer URL to FSF address cleanup packaging harder fixup -Wshorten-64-to-32 warnings cleanup: avoid shadowing rb_str_subseq cleanup: avoid shadowing rb_ary_subseq pkg.mk: synchronize with my other projects gemspec: remove invalid rdoc_options gemspec: use SPDX license abbreviation === kgio 2.9.2 - avoid deprecated/removed function / 2014-02-15 09:27 UTC This release is for compatibility with future releases of mainline ruby, as rb_thread_blocking_region is removed in r44955 of ruby trunk This also avoids deprecation warnings fo rb_thread_blocking_region 2.0 and 2.1. === kgio 2.9.1 - various Ruby 1.8.7 fixes / 2014-02-05 17:50 UTC Thanks to Christopher Rigor for this release. Eric Wong (2): only define and test kgio_syssend on 1.9+ various 1.8.7 fixes === kgio 2.9.0 - cleanups, bug fixes, minor improvements / 2014-02-04 03:09 UTC This adds a new kgio_syssend method for Ruby 1.9+ which behaves like BasicSocket#send, but will not raise exceptions on EAGAIN. Eric Wong (12): test_poll: remove race prone test_poll_EINTR_changed test tryopen: remove RARRAY_PTR usage in init read_write: remove the rest of RARRAY_PTR usage my_writev: stylistic fixes Rakefile: kill raa_update task avoid downsizing casts connect: constify RSTRING-related things set RSTRING_MODIFIED where appropriate for Rubinius split read_write.c into {read,write,writev}.c add kgio_syssend method to wrap send(2) write: correct check for various blocking regions tryopen: additional debug information for bad Errno values Hleb Valoshka (1): Don't use deprecated api === kgio 2.8.1 - minor improvements and test fixes / 2013-09-11 00:22 UTC Improved error reporting for kgio_accept/kgio_tryaccept. Minor size reduction throughout. There are also several test case fixes for race conditions. Thanks to Hleb Valoshka and the Debian project for all the help with this release! Eric Wong (7): check syscall returns against < 0 instead of == -1 accept: more informative exception on unknown family test_tryopen: skip EACCES test when euid == 0 test/lib_read_write: account for larger-than-normal pipes test_poll: avoid potentially thread-unsafe test test_poll: preserve original trap(:USR1) handler test_poll: be less dependent on signal handler ordering Hleb Valoshka (4): Change prefix of temporary sockets to prevent races Don't dump 20M in case of failure Create own directory for every unix socket in unit tests Close tempfile and unlink it immediately. === kgio 2.8.0 - TCP Fast Open, writev/trywritev / 2013-01-18 10:52 UTC TCP Fast Open in Linux 3.7 and later is now supported in the client via Kgio::Socket#kgio_fastopen. This release also adds the kgio_writev and kgio_trywritev methods, thanks to funny-falcon === kgio 2.7.4 - small fixes and cleanups / 2012-03-24 01:15 UTC Fix build for platforms lacking both TCP_CORK _and_ TCP_NOPUSH There are many test case fixes and cleanups, too. === kgio 2.7.3 - compatibility fixes / 2012-03-15 07:11 UTC Fixed build and autopush support under Debian GNU/kFreeBSD. Test case fixes for timing-sensitive tests. === kgio 2.7.2 - for older, and older Rubies / 2012-01-08 03:42 UTC Fix a missing #include for Ruby 1.8.5 users. No need to upgrade to this (nor 2.7.1) if you're on a modern version of Ruby. === kgio 2.7.1 - compatibility with older Rubies / 2012-01-08 01:59 UTC This release fixes some compatibility issues with people stuck on older versions of Ruby/RubyGems. * define RARRAY_PTR/RARRAY_LEN macros for Ruby 1.8.6 * test/test_autopush: skip strace tests if not available * gemspec: disable development dependencies for old systems === kgio 2.7.0 - minor updates / 2011-12-13 06:16 UTC When running under Ruby trunk/2.0.0dev, all IO objects created by kgio will be close-on-exec by default to match the (future) 2.0.0 behavior. accept()ed sockets in kgio have always been close-on-exec by default.. Singleton Kgio.accept_* methods are deprecated as the kgio_accept/kgio_tryaccept methods all take an additional flags argument. There are various, test, documentation, and error message improvements. === kgio 2.6.0 - minor feature update and cleanups / 2011-07-15 02:01 UTC We now export SOCK_NONBLOCK, SOCK_CLOEXEC constants in the Kgio namespace to make kgio_tryaccept/kgio_accept easier-to-use. There are also some minor internal cleanups. === kgio 2.5.0 - a minor feature update / 2011-06-20 19:30 UTC * Kgio::File.tryopen method added. It is like File.open but won't raise exceptions. The Kgio::File class includes Kgio::PipeMethods, so FIFOs opened through this get the kgio_* API. * The kgio_wait_*able methods in Kgio::DefaultWaiters now accept an optional timeout argument. === kgio 2.4.2 - OpenSolaris build fix / 2011-06-14 18:41 UTC * adds -lnsl and -lsocket checks for OpenSolaris === kgio 2.4.1 - Kgio.poll avoids EINTR, really / 2011-05-21 02:54 UTC This release fixes a race condition that could allow Errno::EINTR to be raised even though the 2.4.0 release was supposed to stop that. Nobody uses Kgio.poll, really, so this shouldn't be an issue for real code, yet. === kgio 2.4.0 - portability fixes and more / 2011-05-05 22:58 UTC == All supported platforms (*nix + MRI 1.8+, Rubinius) * OpenBSD (and possibly other *BSD) fixes, thanks to Jeremy Evans. * kgio_accept and kgio_tryaccept now take an optional second argument for flags (like the accept4() flags argument). == Ruby 1.9-only things * Kgio.poll no longer raises Errno::EINTR to match IO.select. == Ruby 1.9 trunk things * close() on an active FD in a different thread is better handled/detected. * copy-on-write for strings is properly triggered === kgio 2.3.3 - minor fixes / 2011-03-15 12:09 UTC We no longer over-allocate memory for Kgio.poll (1.9.x-only). Under Ruby 1.9.3dev, we also use rb_thread_io_blocking_region to properly deal with cross-thread IO#close. === kgio 2.3.2 - OpenBSD build fix / 2011-02-15 16:56 UTC Thanks to Jeremy Evans, this release fixes the build under OpenBSD. === kgio 2.3.1 - compatibility fix / 2011-02-14 00:51 UTC * connect.c: disable AI_NUMERICSERV It's not needed since we already verify the service is a numeric port. AI_NUMERICSERV is not available in older glibc (<2.3.4) and probably other old systems. === kgio 2.3.0 - MSG_PEEK and poll(2) support / 2011-02-09 10:26 UTC recv() with MSG_PEEK for sockets is added with the try* interface. SocketMethods#kgio_trypeek and SocketMethods#kgio_peek or Kgio.trypeek for non-Kgio-enabled sockets. For Ruby 1.9 only: poll(2) is exposed via the Kgio.poll singleton method and should provide an alternative for IO.select users. Both of these new features should work well on modern Unix-like operating systems. === kgio 2.2.0 - kinder, gentler I/O for the Internets / 2011-02-04 03:07 UTC * sockets accept()ed by a TCP_NOPUSH/TCP_CORK listener automatically flush on kgio_*read calls if there is pending data. "Kgio.autopush = false" disables this globally, and Kgio::Socket also get "kgio_autopush=" to enable/disable on a per-object individual basis. * ECONNRESET exceptions get empty backtraces for kgio_*read. There's nothing a programmer can do about these, so there's no point in going through the expensive backtrace generation process. * Kgio.try* singleton methods added for working with non-Kgio enhanced objects. No more needing to use Object#extend and blowing away your method cache to make existing I/O objects kinder and gentler. * IPv6 support should be complete, systems without a native getaddrinfo(3) are now unsupported (and will remain so unless somebody complains). There should be no other backwards-incompatible changes other than requiring getaddrinfo(3) and friends for IPv6 support. === kgio 2.1.1 - one small Rubinius fix / 2010-12-26 02:08 UTC We now avoid errno side-effects in kgio_wait_*able methods. This affects Rubinius, but may affect other Ruby platforms (particularly those that use stdio) as well. === kgio 2.1.0 - accept improvements and fixes / 2010-12-26 01:07 UTC kgio_accept and kgio_tryaccept now take an optional argument to override the default Kgio::Socket class that is returned. These methods also fall back to using regular accept() if kgio was built on a system with accept4() and later run on a system without accept4(). === kgio 2.0.0 - major internal API changes / 2010-11-19 01:18 UTC (no code changes from 2.0.0pre1) This release should make Kgio easier and more consistent to use across a variety of libraries/applications. The global Kgio.wait_*able(=) accessor methods are gone in favor of having default kgio_wait_readable and kgio_wait_writable methods added to all Kgio-using classes. Sub-classes may (and are encouraged to) redefine these if needed. Eric Wong (7): expand Kgio::*#kgio_read! documentation prefer symbolic names for waiting read/writability EOFError message matches Ruby's README: Gemcutter => RubyGems.org update documentation with mailing list info add default kgio_wait_*able methods switch entirely to kgio_wait_*able methods === kgio 2.0.0pre1 - major internal API changes / 2010-11-18 23:16 UTC This release should make Kgio easier and more consistent to use across a variety of libraries/applications. The global Kgio.wait_*able(=) accessor methods are gone in favor of having default kgio_wait_readable and kgio_wait_writable methods added to all Kgio-using classes. Sub-classes may (and are encouraged to) redefine these if needed. Eric Wong (7): expand Kgio::*#kgio_read! documentation prefer symbolic names for waiting read/writability EOFError message matches Ruby's README: Gemcutter => RubyGems.org update documentation with mailing list info add default kgio_wait_*able methods switch entirely to kgio_wait_*able methods === kgio 1.3.1 - fix zero-length reads / 2010-10-08 22:20 UTC kgio_read and kgio_tryread will now return an empty string when a length of zero is specified instead of nil (which would signal an EOF). This emulates the behavior of IO#read, IO#readpartial, IO#sysread, IO#read_nonblock in core Ruby for consistency. === kgio 1.3.0 - bug and usability fixes / 2010-10-08 03:03 UTC * make Kgio::WaitWritable and Kgio::WaitReadable symbols * trywrite: fix stupid off-by-one error causing corrupt writes on retries === kgio 1.2.1 - doc and *BSD workarounds / 2010-10-07 07:20 UTC This fixes our accept4() wrapper which did not work as expected on some *BSD-based systems due to fcntl(fd, F_GETFL) returning false information. Linux 2.6+ users are unnaffected, including those without accept4(). Also some RDoc fixes. === kgio 1.2.0 - cleanups and minor improvements / 2010-10-05 23:14 UTC The C extension is now split into several files for ease-of-maintenance. Slightly more common, client-triggerable exceptions (EOFError, Errno::EPIPE, Errno::ECONNRESET) are now less expensive as they are generated without backtraces. === kgio 1.1.0 - flexible accept methods / 2010-09-29 01:17 UTC * alternate classes may now be returned by accept/tryaccept by setting Kgio.accept_class= === kgio 1.0.1 - compatibility fixes / 2010-09-28 03:00 UTC * add compatibility for ancient Rubies (1.8.6) * linux: fix accept4() support for newer Linux === kgio 1.0.0 - initial release / 2010-09-28 00:29 UTC A kinder, gentler I/O library for Ruby kgio-2.11.2/lib/0000755000004100000410000000000013241032461013320 5ustar www-datawww-datakgio-2.11.2/lib/kgio.rb0000644000004100000410000000204113241032461014573 0ustar www-datawww-data# -*- encoding: binary -*- require 'socket' # See the {README}[link:index.html] module Kgio # The IPv4 address of UNIX domain sockets, useful for creating # Rack (and CGI) servers that also serve HTTP traffic over # UNIX domain sockets. LOCALHOST = '127.0.0.1' # Kgio::PipeMethods#kgio_tryread and Kgio::SocketMethods#kgio_tryread will # return :wait_readable when waiting for a read is required. WaitReadable = :wait_readable # PipeMethods#kgio_trywrite and SocketMethods#kgio_trywrite will return # :wait_writable when waiting for a read is required. WaitWritable = :wait_writable end require 'kgio_ext' # use Kgio::Pipe.popen and Kgio::Pipe.new instead of IO.popen # and IO.pipe to get PipeMethods#kgio_read and PipeMethod#kgio_write # methods. class Kgio::Pipe < IO include Kgio::PipeMethods class << self # call-seq: # # rd, wr = Kgio::Pipe.new # # This creates a new pipe(7) with Kgio::Pipe objects that respond # to PipeMethods#kgio_read and PipeMethod#kgio_write alias new pipe end end kgio-2.11.2/.manifest0000644000004100000410000000266413241032461014371 0ustar www-datawww-data.document .gitignore .manifest .olddoc.yml COPYING GIT-VERSION-FILE GIT-VERSION-GEN GNUmakefile HACKING ISSUES LATEST LICENSE NEWS README TODO archive/.gitignore archive/slrnpull.conf ext/kgio/accept.c ext/kgio/ancient_ruby.h ext/kgio/autopush.c ext/kgio/blocking_io_region.h ext/kgio/broken_system_compat.h ext/kgio/connect.c ext/kgio/extconf.rb ext/kgio/kgio.h ext/kgio/kgio_ext.c ext/kgio/missing_accept4.h ext/kgio/my_fileno.h ext/kgio/nonblock.h ext/kgio/poll.c ext/kgio/read.c ext/kgio/set_file_path.h ext/kgio/sock_for_fd.h ext/kgio/tryopen.c ext/kgio/wait.c ext/kgio/write.c ext/kgio/writev.c kgio.gemspec lib/kgio.rb pkg.mk setup.rb test/lib_read_write.rb test/lib_server_accept.rb test/test_accept_class.rb test/test_accept_flags.rb test/test_autopush.rb test/test_connect_fd_leak.rb test/test_cross_thread_close.rb test/test_default_wait.rb test/test_kgio_addr.rb test/test_no_dns_on_tcp_connect.rb test/test_peek.rb test/test_pipe_popen.rb test/test_pipe_read_write.rb test/test_poll.rb test/test_singleton_read_write.rb test/test_socket.rb test/test_socketpair_read_write.rb test/test_syssend.rb test/test_tcp6_client_read_server_write.rb test/test_tcp_client_read_server_write.rb test/test_tcp_connect.rb test/test_tcp_server.rb test/test_tcp_server_read_client_write.rb test/test_tfo.rb test/test_tryopen.rb test/test_unix_client_read_server_write.rb test/test_unix_connect.rb test/test_unix_server.rb test/test_unix_server_read_client_write.rb kgio-2.11.2/test/0000755000004100000410000000000013241032461013531 5ustar www-datawww-datakgio-2.11.2/test/test_default_wait.rb0000644000004100000410000000175713241032461017577 0ustar www-datawww-datarequire 'test/unit' require 'io/nonblock' $-w = true require 'kgio' class TestDefaultWait < Test::Unit::TestCase def test_socket_pair a, b = Kgio::UNIXSocket.pair assert_equal a, a.kgio_wait_writable a.syswrite('.') assert_equal b, b.kgio_wait_readable end def test_pipe a, b = Kgio::Pipe.new assert_equal b, b.kgio_wait_writable b.syswrite('.') assert_equal a, a.kgio_wait_readable end def test_wait_readable_timed a, b = Kgio::Pipe.new t0 = Time.now assert_nil a.kgio_wait_readable(1.1) diff = Time.now - t0 assert_in_delta diff, 1.1, 0.2 b.kgio_write '.' assert_equal a, a.kgio_wait_readable(1.1) end def test_wait_writable_timed a, b = Kgio::Pipe.new buf = "*" * 65536 true until Symbol === b.kgio_trywrite(buf) t0 = Time.now assert_nil b.kgio_wait_writable(1.1) diff = Time.now - t0 assert_in_delta diff, 1.1, 0.2 a.kgio_read(16384) assert_equal b, b.kgio_wait_writable(1.1) end end kgio-2.11.2/test/test_syssend.rb0000644000004100000410000000213013241032461016601 0ustar www-datawww-datarequire 'test/unit' require 'kgio' class TestKgioSyssend < Test::Unit::TestCase def setup @host = '127.0.0.1' || ENV["TEST_HOST"] end def test_syssend srv = Kgio::TCPServer.new(@host, 0) port = srv.addr[1] client = TCPSocket.new(@host, port) acc = srv.kgio_accept th = Thread.new { client.readpartial(4) } sleep(0.05) assert_nil acc.kgio_syssend("HI", Socket::MSG_DONTWAIT | Socket::MSG_MORE) assert_nil acc.kgio_syssend("HI", Socket::MSG_DONTWAIT) assert_equal "HIHI", th.value buf = "*" * 123 res = [] case rv = acc.kgio_syssend(buf, Socket::MSG_DONTWAIT) when nil when String res << rv when Symbol res << rv break end while true assert_equal :wait_writable, res.last if res.size > 1 assert_kind_of String, res[-2] else warn "res too small" end # blocking th = Thread.new { loop { acc.kgio_syssend("ZZZZ", 0) } } assert_nil th.join(0.1) ensure [ srv, acc, client ].each { |io| io.close if io } end end if RUBY_PLATFORM =~ /linux/ && Socket.const_defined?(:MSG_MORE) kgio-2.11.2/test/test_tfo.rb0000644000004100000410000000345513241032461015714 0ustar www-datawww-datarequire 'test/unit' require 'kgio' class TestTFO < Test::Unit::TestCase def test_constants if `uname -s`.chomp == "Linux" && `uname -r`.to_f >= 3.7 assert_equal 23, Kgio::TCP_FASTOPEN assert_equal 0x20000000, Kgio::MSG_FASTOPEN end end def fastopen_ok? if RUBY_PLATFORM =~ /linux/ tfo = File.read("/proc/sys/net/ipv4/tcp_fastopen").to_i client_enable = 1 server_enable = 2 enable = client_enable | server_enable (tfo & enable) == enable else false end end def test_tfo_client_server unless fastopen_ok? warn "TCP Fast Open not enabled on this system (check kernel docs)" return end addr = '127.0.0.1' qlen = 1024 s = Kgio::TCPServer.new(addr, 0) s.setsockopt(:TCP, Kgio::TCP_FASTOPEN, qlen) port = s.local_address.ip_port addr = Socket.pack_sockaddr_in(port, addr) c = Kgio::Socket.new(:INET, :STREAM) assert_nil c.kgio_fastopen("HELLO", addr) a = s.accept assert_equal "HELLO", a.read(5) c.close a.close # ensure empty sends work c = Kgio::Socket.new(:INET, :STREAM) assert_nil c.kgio_fastopen("", addr) a = s.accept Thread.new { c.close } assert_nil a.read(1) a.close # try a monster packet buf = 'x' * (1024 * 1024 * 320) c = Kgio::Socket.new(:INET, :STREAM) thr = Thread.new do a = s.accept assert_equal buf.size, a.read(buf.size).size a.close end assert_nil c.kgio_fastopen(buf, addr) thr.join c.close # allow timeouts c = Kgio::Socket.new(:INET, :STREAM) c.setsockopt(:SOCKET, :SNDTIMEO, [ 0, 10 ].pack("l_l_")) unsent = c.kgio_fastopen(buf, addr) c.close assert_equal s.accept.read.size + unsent.size, buf.size end if defined?(Addrinfo) && defined?(Kgio::TCP_FASTOPEN) end kgio-2.11.2/test/test_socketpair_read_write.rb0000644000004100000410000000025713241032461021472 0ustar www-datawww-datarequire './test/lib_read_write.rb' class TestKgioUNIXSocketPair < Test::Unit::TestCase def setup @rd, @wr = Kgio::UNIXSocket.pair end include LibReadWriteTest end kgio-2.11.2/test/test_tryopen.rb0000644000004100000410000000430513241032461016617 0ustar www-datawww-datarequire 'tempfile' require 'test/unit' $-w = true require 'kgio' class TestTryopen < Test::Unit::TestCase def test_tryopen_success tmp = Kgio::File.tryopen(__FILE__) tmp.respond_to?(:close_on_exec?) and assert_equal(RUBY_VERSION.to_f >= 2.0, tmp.close_on_exec?) assert_kind_of File, tmp assert_equal File.read(__FILE__), tmp.read assert_equal __FILE__, tmp.path assert_equal __FILE__, tmp.to_path tmp.close end def test_tryopen_ENOENT tmp = Tempfile.new "tryopen" path = tmp.path tmp.close! tmp = Kgio::File.tryopen(path) assert_equal :ENOENT, tmp end def test_tryopen_EACCES tmp = Tempfile.new "tryopen" File.chmod 0000, tmp.path tmp = Kgio::File.tryopen(tmp.path) if Process.euid == 0 assert_kind_of Kgio::File, tmp warn "cannot test EACCES when euid == 0" else assert_equal(:EACCES, tmp) end end def test_tryopen_readwrite tmp = Tempfile.new "tryopen" file = Kgio::File.tryopen(tmp.path, IO::RDWR) file.syswrite "FOO" assert_equal "FOO", tmp.sysread(3) end def test_tryopen_try_readwrite tmp = Tempfile.new "tryopen" file = Kgio::File.tryopen(tmp.path, IO::RDWR) assert_nil file.kgio_trywrite("FOO") file.rewind assert_equal "FOO", file.kgio_tryread(3) end def test_tryopen_mode tmp = Tempfile.new "tryopen" path = tmp.path tmp.close! file = Kgio::File.tryopen(path, IO::RDWR|IO::CREAT, 0000) assert_equal 0100000, File.stat(path).mode ensure File.unlink path end require "benchmark" def test_benchmark nr = 1000000 tmp = Tempfile.new('tryopen') file = tmp.path Benchmark.bmbm do |x| x.report("tryopen (OK)") do nr.times { Kgio::File.tryopen(file).close } end x.report("open (OK)") do nr.times { File.readable?(file) && File.open(file).close } end end tmp.close! assert_equal :ENOENT, Kgio::File.tryopen(file) Benchmark.bmbm do |x| x.report("tryopen (ENOENT)") do nr.times { Kgio::File.tryopen(file) } end x.report("open (ENOENT)") do nr.times { File.readable?(file) && File.open(file) } end end end if ENV["BENCHMARK"] end kgio-2.11.2/test/test_poll.rb0000644000004100000410000000522713241032461016071 0ustar www-datawww-datarequire 'test/unit' $-w = true require 'kgio' class TestPoll < Test::Unit::TestCase def teardown [ @rd, @wr ].each { |io| io.close unless io.closed? } end def setup @rd, @wr = IO.pipe end def test_constants assert_kind_of Integer, Kgio::POLLIN assert_kind_of Integer, Kgio::POLLOUT assert_kind_of Integer, Kgio::POLLPRI assert_kind_of Integer, Kgio::POLLHUP assert_kind_of Integer, Kgio::POLLERR assert_kind_of Integer, Kgio::POLLNVAL end def test_poll_symbol set = { @rd => :wait_readable, @wr => :wait_writable } res = Kgio.poll(set) assert_equal({@wr => Kgio::POLLOUT}, res) assert_equal set.object_id, res.object_id end def test_poll_integer set = { @wr => Kgio::POLLOUT|Kgio::POLLHUP } res = Kgio.poll(set) assert_equal({@wr => Kgio::POLLOUT}, res) assert_equal set.object_id, res.object_id end def test_poll_timeout t0 = Time.now res = Kgio.poll({}, 10) diff = Time.now - t0 assert diff >= 0.010, "diff=#{diff}" assert_nil res end def test_poll_close foo = nil thr = Thread.new { sleep 0.100; @wr.close } t0 = Time.now res = Kgio.poll({@rd => Kgio::POLLIN}) diff = Time.now - t0 thr.join assert_equal([ @rd ], res.keys) assert diff >= 0.010, "diff=#{diff}" end def test_signal_close orig = trap(:USR1) { @rd.close } res = nil thr = Thread.new { sleep 0.100; Process.kill(:USR1, $$) } t0 = Time.now assert_raises(IOError) do result = Kgio.poll({@rd => Kgio::POLLIN}) result.each_key { |io| io.read_nonblock(1) } end diff = Time.now - t0 thr.join assert diff >= 0.010, "diff=#{diff}" ensure trap(:USR1, orig) end def test_poll_EINTR ok = false orig = trap(:USR1) { ok = true } thr = Thread.new do sleep 0.100 Process.kill(:USR1, $$) end t0 = Time.now res = Kgio.poll({@rd => Kgio::POLLIN}, 1000) diff = Time.now - t0 thr.join assert_nil res assert diff >= 1.0, "diff=#{diff}" assert ok ensure trap(:USR1, orig) end def test_poll_signal_torture usr1 = 0 empty = 0 nr = 100 set = { @rd => Kgio::POLLIN } orig = trap(:USR1) { usr1 += 1 } pid = fork do trap(:USR1, "DEFAULT") sleep 0.1 ppid = Process.ppid nr.times { Process.kill(:USR1, ppid); sleep 0.05 } @wr.syswrite('.') exit!(0) end empty += 1 until Kgio.poll(set.dup, 100) _, status = Process.waitpid2(pid) assert status.success?, status.inspect assert usr1 > 0, "usr1: #{usr1}" ensure trap(:USR1, orig) end unless RUBY_PLATFORM =~ /kfreebsd-gnu/ end if Kgio.respond_to?(:poll) kgio-2.11.2/test/test_socket.rb0000644000004100000410000000057613241032461016415 0ustar www-datawww-datarequire 'test/unit' require 'kgio' class TestKgioSocket < Test::Unit::TestCase def test_socket_args s = Kgio::Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) assert_kind_of Socket, s assert_instance_of Kgio::Socket, s s = Kgio::Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) assert_kind_of Socket, s assert_instance_of Kgio::Socket, s end end kgio-2.11.2/test/lib_server_accept.rb0000644000004100000410000000433113241032461017532 0ustar www-datawww-datarequire 'test/unit' require 'fcntl' require 'io/nonblock' require 'fileutils' $-w = true require 'kgio' module LibServerAccept def teardown @srv.close unless @srv.closed? FileUtils.remove_entry_secure(@tmpdir) if defined?(@tmpdir) Kgio.accept_cloexec = true Kgio.accept_nonblock = false end def test_tryaccept_success a = client_connect IO.select([@srv]) b = @srv.kgio_tryaccept assert_kind_of Kgio::Socket, b assert_equal @host, b.kgio_addr end def test_tryaccept_flags a = client_connect IO.select([@srv]) b = @srv.kgio_tryaccept nil, 0 assert_kind_of Kgio::Socket, b assert_equal 0, b.fcntl(Fcntl::F_GETFD) end def test_blocking_accept_flags a = client_connect IO.select([@srv]) b = @srv.kgio_accept nil, 0 assert_kind_of Kgio::Socket, b assert_equal 0, b.fcntl(Fcntl::F_GETFD) end def test_tryaccept_fail assert_equal nil, @srv.kgio_tryaccept end def test_blocking_accept t0 = Time.now pid = fork { sleep 1; a = client_connect; sleep } b = @srv.kgio_accept elapsed = Time.now - t0 assert_kind_of Kgio::Socket, b assert_equal @host, b.kgio_addr Process.kill(:KILL, pid) Process.waitpid(pid) assert elapsed >= 1, "elapsed: #{elapsed}" end def test_blocking_accept_with_nonblock_socket @srv.nonblock = true t0 = Time.now pid = fork { sleep 1; a = client_connect; sleep } b = @srv.kgio_accept elapsed = Time.now - t0 assert_kind_of Kgio::Socket, b assert_equal @host, b.kgio_addr Process.kill(:KILL, pid) Process.waitpid(pid) assert elapsed >= 1, "elapsed: #{elapsed}" t0 = Time.now pid = fork { sleep 6; a = client_connect; sleep } b = @srv.kgio_accept elapsed = Time.now - t0 assert_kind_of Kgio::Socket, b assert_equal @host, b.kgio_addr Process.kill(:KILL, pid) Process.waitpid(pid) assert elapsed >= 6, "elapsed: #{elapsed}" t0 = Time.now pid = fork { sleep 1; a = client_connect; sleep } b = @srv.kgio_accept elapsed = Time.now - t0 assert_kind_of Kgio::Socket, b assert_equal @host, b.kgio_addr Process.kill(:KILL, pid) Process.waitpid(pid) assert elapsed >= 1, "elapsed: #{elapsed}" end end kgio-2.11.2/test/test_pipe_read_write.rb0000644000004100000410000000023613241032461020260 0ustar www-datawww-datarequire './test/lib_read_write.rb' class TestKgioPipe < Test::Unit::TestCase def setup @rd, @wr = Kgio::Pipe.new end include LibReadWriteTest end kgio-2.11.2/test/test_connect_fd_leak.rb0000644000004100000410000000055013241032461020213 0ustar www-datawww-datarequire 'test/unit' require 'io/nonblock' $-w = true require 'kgio' class TestConnectFDLeak < Test::Unit::TestCase def test_unix_socket nr = 0 path = "/non/existent/path" assert(! File.exist?(path), "#{path} should not exist") begin sock = Kgio::UNIXSocket.new(path) rescue Errno::ENOENT end while (nr += 1) < 10000 end end kgio-2.11.2/test/test_tcp_server_read_client_write.rb0000644000004100000410000000050113241032461023030 0ustar www-datawww-datarequire './test/lib_read_write' class TesTcpServerReadClientWrite < Test::Unit::TestCase def setup @host = ENV["TEST_HOST"] || '127.0.0.1' @srv = Kgio::TCPServer.new(@host, 0) @port = @srv.addr[1] @wr = Kgio::TCPSocket.new(@host, @port) @rd = @srv.kgio_accept end include LibReadWriteTest end kgio-2.11.2/test/test_tcp_connect.rb0000644000004100000410000000442113241032461017415 0ustar www-datawww-datarequire 'test/unit' require 'io/nonblock' $-w = true require 'kgio' class SubSocket < Kgio::Socket attr_accessor :foo def kgio_wait_writable @foo = "waited" end end class TestKgioTcpConnect < Test::Unit::TestCase def setup @host = ENV["TEST_HOST"] || '127.0.0.1' @srv = Kgio::TCPServer.new(@host, 0) @port = @srv.addr[1] @addr = Socket.pack_sockaddr_in(@port, @host) end def teardown @srv.close unless @srv.closed? Kgio.accept_cloexec = true Kgio.accept_nonblock = false end def test_new sock = Kgio::Socket.new(@addr) assert_kind_of Kgio::Socket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] assert_equal nil, sock.kgio_write("HELLO") sock.respond_to?(:close_on_exec?) and assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) end def test_start sock = Kgio::Socket.start(@addr) sock.respond_to?(:close_on_exec?) and assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) assert_kind_of Kgio::Socket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] assert_equal nil, sock.kgio_write("HELLO") end def test_tcp_socket_new_invalid assert_raises(ArgumentError) { Kgio::TCPSocket.new('example.com', 80) } assert_raises(ArgumentError) { Kgio::TCPSocket.new('999.999.999.999', 80) } assert_raises(TypeError) { Kgio::TCPSocket.new("127.0.0.1", "http") } assert_raises(TypeError) { Kgio::TCPSocket.new('example.com', "http") } end def test_tcp_socket_new sock = Kgio::TCPSocket.new(@host, @port) sock.respond_to?(:close_on_exec?) and assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) assert_instance_of Kgio::TCPSocket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] assert_equal nil, sock.kgio_write("HELLO") end def test_socket_start sock = SubSocket.start(@addr) assert_nil sock.foo ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] assert_equal nil, sock.kgio_write("HELLO") end def test_wait_writable_set sock = SubSocket.new(@addr) assert_equal "waited", sock.foo if RUBY_PLATFORM =~ /linux/ IO.select(nil, [sock]) if RUBY_PLATFORM !~ /linux/ assert_equal nil, sock.kgio_write("HELLO") end end kgio-2.11.2/test/test_tcp6_client_read_server_write.rb0000644000004100000410000000116713241032461023127 0ustar www-datawww-datarequire './test/lib_read_write' begin tmp = TCPServer.new(ENV["TEST_HOST6"] || '::1', 0) ipv6_enabled = true rescue => e warn "skipping IPv6 tests, host does not seem to be IPv6 enabled:" warn " #{e.class}: #{e}" ipv6_enabled = false end class TestTcp6ClientReadServerWrite < Test::Unit::TestCase def setup @host = ENV["TEST_HOST6"] || '::1' @srv = Kgio::TCPServer.new(@host, 0) @port = @srv.addr[1] @wr = Kgio::TCPSocket.new(@host, @port) @rd = @srv.kgio_accept assert_equal Socket.unpack_sockaddr_in(@rd.getpeername)[-1], @rd.kgio_addr end include LibReadWriteTest end if ipv6_enabled kgio-2.11.2/test/test_unix_client_read_server_write.rb0000644000004100000410000000063313241032461023233 0ustar www-datawww-datarequire './test/lib_read_write' require 'tempfile' require 'tmpdir' class TestUnixClientReadServerWrite < Test::Unit::TestCase def setup @tmpdir = Dir.mktmpdir('kgio_unix_0') tmp = Tempfile.new('kgio_unix_0', @tmpdir) @path = tmp.path tmp.close! @srv = Kgio::UNIXServer.new(@path) @rd = Kgio::UNIXSocket.new(@path) @wr = @srv.kgio_tryaccept end include LibReadWriteTest end kgio-2.11.2/test/test_unix_server_read_client_write.rb0000644000004100000410000000063313241032461023233 0ustar www-datawww-datarequire './test/lib_read_write' require 'tempfile' require 'tmpdir' class TestUnixServerReadClientWrite < Test::Unit::TestCase def setup @tmpdir = Dir.mktmpdir('kgio_unix_3') tmp = Tempfile.new('kgio_unix_3', @tmpdir) @path = tmp.path tmp.close! @srv = Kgio::UNIXServer.new(@path) @wr = Kgio::UNIXSocket.new(@path) @rd = @srv.kgio_tryaccept end include LibReadWriteTest end kgio-2.11.2/test/test_singleton_read_write.rb0000644000004100000410000000102713241032461021324 0ustar www-datawww-datarequire 'test/unit' $-w = true require 'kgio' class TestSingletonReadWrite < Test::Unit::TestCase def test_unix_socketpair a, b = UNIXSocket.pair Kgio.trywrite(a, "HELLO") buf = "" assert_equal "HELLO", Kgio.tryread(b, 5, buf) assert_equal "HELLO", buf assert_equal :wait_readable, Kgio.tryread(b, 5) end def test_arg_error assert_raises(ArgumentError) { Kgio.tryread } assert_raises(ArgumentError) { Kgio.tryread($stdin) } assert_raises(ArgumentError) { Kgio.trywrite($stdout) } end end kgio-2.11.2/test/test_unix_server.rb0000644000004100000410000000063413241032461017471 0ustar www-datawww-datarequire 'tempfile' require 'tmpdir' require './test/lib_server_accept' class TestKgioUNIXServer < Test::Unit::TestCase def setup @tmpdir = Dir.mktmpdir('kgio_unix_2') tmp = Tempfile.new('kgio_unix_2', @tmpdir) @path = tmp.path tmp.close! @srv = Kgio::UNIXServer.new(@path) @host = '127.0.0.1' end def client_connect UNIXSocket.new(@path) end include LibServerAccept end kgio-2.11.2/test/test_accept_class.rb0000644000004100000410000000330213241032461017537 0ustar www-datawww-datarequire 'test/unit' require 'io/nonblock' $-w = true require 'kgio' class TestAcceptClass < Test::Unit::TestCase class FooSocket < Kgio::Socket end def setup assert_equal Kgio::Socket, Kgio.accept_class end def teardown Kgio.accept_class = nil assert_equal Kgio::Socket, Kgio.accept_class end def test_tcp_socket Kgio.accept_class = Kgio::TCPSocket assert_equal Kgio::TCPSocket, Kgio.accept_class end def test_invalid assert_raises(TypeError) { Kgio.accept_class = TCPSocket } assert_equal Kgio::Socket, Kgio.accept_class end def test_accepted_class @host = ENV["TEST_HOST"] || '127.0.0.1' @srv = Kgio::TCPServer.new(@host, 0) @port = @srv.addr[1] Kgio.accept_class = Kgio::TCPSocket client = TCPSocket.new(@host, @port) assert_instance_of Kgio::TCPSocket, @srv.kgio_accept client = TCPSocket.new(@host, @port) IO.select([@srv]) assert_instance_of Kgio::TCPSocket, @srv.kgio_tryaccept Kgio.accept_class = nil client = TCPSocket.new(@host, @port) assert_instance_of Kgio::Socket, @srv.kgio_accept client = TCPSocket.new(@host, @port) IO.select([@srv]) assert_instance_of Kgio::Socket, @srv.kgio_tryaccept Kgio.accept_class = Kgio::UNIXSocket client = TCPSocket.new(@host, @port) assert_instance_of Kgio::UNIXSocket, @srv.kgio_accept client = TCPSocket.new(@host, @port) IO.select([@srv]) assert_instance_of Kgio::UNIXSocket, @srv.kgio_tryaccept client = TCPSocket.new(@host, @port) assert_instance_of FooSocket, @srv.kgio_accept(FooSocket) client = TCPSocket.new(@host, @port) IO.select([@srv]) assert_instance_of FooSocket, @srv.kgio_tryaccept(FooSocket) end end kgio-2.11.2/test/test_pipe_popen.rb0000644000004100000410000000050513241032461017253 0ustar www-datawww-datarequire 'test/unit' require 'io/nonblock' $-w = true require 'kgio' class TestPipePopen < Test::Unit::TestCase def test_popen io = Kgio::Pipe.popen("sleep 1 && echo HI") assert_equal :wait_readable, io.kgio_tryread(2) sleep 1.5 assert_equal "HI\n", io.kgio_read(3) assert_nil io.kgio_read(5) end end kgio-2.11.2/test/test_accept_flags.rb0000644000004100000410000000277313241032461017541 0ustar www-datawww-datarequire 'test/unit' require 'fcntl' require 'io/nonblock' $-w = true require 'kgio' class TestAcceptFlags < Test::Unit::TestCase def test_accept_flags @host = ENV["TEST_HOST"] || '127.0.0.1' @srv = Kgio::TCPServer.new(@host, 0) @port = @srv.addr[1] client = TCPSocket.new(@host, @port) accepted = @srv.kgio_accept(nil, Kgio::SOCK_NONBLOCK) assert_instance_of Kgio::Socket, accepted flags = accepted.fcntl(Fcntl::F_GETFD) assert_equal 0, flags & Fcntl::FD_CLOEXEC assert_nil client.close assert_nil accepted.close client = TCPSocket.new(@host, @port) accepted = @srv.kgio_accept(nil, Kgio::SOCK_CLOEXEC) assert_instance_of Kgio::Socket, accepted flags = accepted.fcntl(Fcntl::F_GETFD) assert_equal Fcntl::FD_CLOEXEC, flags & Fcntl::FD_CLOEXEC assert_nil client.close assert_nil accepted.close client = TCPSocket.new(@host, @port) accepted = @srv.kgio_accept(nil, Kgio::SOCK_CLOEXEC|Kgio::SOCK_NONBLOCK) assert_instance_of Kgio::Socket, accepted flags = accepted.fcntl(Fcntl::F_GETFD) assert_equal Fcntl::FD_CLOEXEC, flags & Fcntl::FD_CLOEXEC assert_nil client.close assert_nil accepted.close client = TCPSocket.new(@host, @port) accepted = @srv.kgio_accept(nil, Kgio::SOCK_CLOEXEC|Kgio::SOCK_NONBLOCK) assert_instance_of Kgio::Socket, accepted flags = accepted.fcntl(Fcntl::F_GETFD) assert_equal Fcntl::FD_CLOEXEC, flags & Fcntl::FD_CLOEXEC assert_nil client.close assert_nil accepted.close end end kgio-2.11.2/test/test_tcp_server.rb0000644000004100000410000000045713241032461017277 0ustar www-datawww-datarequire './test/lib_server_accept' class TestKgioTCPServer < Test::Unit::TestCase def setup @host = ENV["TEST_HOST"] || '127.0.0.1' @srv = Kgio::TCPServer.new(@host, 0) @port = @srv.addr[1] end def client_connect TCPSocket.new(@host, @port) end include LibServerAccept end kgio-2.11.2/test/test_peek.rb0000644000004100000410000000150513241032461016042 0ustar www-datawww-datarequire 'test/unit' $-w = true require 'kgio' class TestPeek < Test::Unit::TestCase class EIEIO < Errno::EIO end def teardown @rd.close @wr.close end def test_peek @rd, @wr = Kgio::UNIXSocket.pair @wr.kgio_write "HELLO" assert_equal "HELLO", @rd.kgio_peek(5) assert_equal "HELLO", @rd.kgio_trypeek(5) assert_equal "HELLO", @rd.kgio_read(5) assert_equal :wait_readable, @rd.kgio_trypeek(5) def @rd.kgio_wait_readable raise EIEIO end assert_raises(EIEIO) { @rd.kgio_peek(5) } end def test_peek_singleton @rd, @wr = UNIXSocket.pair @wr.syswrite "HELLO" assert_equal "HELLO", Kgio.trypeek(@rd, 666) assert_equal "HELLO", Kgio.trypeek(@rd, 666) assert_equal "HELLO", Kgio.tryread(@rd, 666) assert_equal :wait_readable, Kgio.trypeek(@rd, 5) end end kgio-2.11.2/test/test_cross_thread_close.rb0000644000004100000410000000113113241032461020756 0ustar www-datawww-datarequire 'test/unit' $-w = true require 'kgio' class TestCrossThreadClose < Test::Unit::TestCase def test_cross_thread_close host = ENV["TEST_HOST"] || '127.0.0.1' srv = Kgio::TCPServer.new(host, 0) thr = Thread.new do begin srv.kgio_accept rescue => e e end end sleep(0.1) until thr.stop? srv.close unless defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" && RUBY_VERSION == "1.9.3" thr.run rescue nil end thr.join assert_kind_of IOError, thr.value end end if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" kgio-2.11.2/test/lib_read_write.rb0000644000004100000410000002704513241032461017041 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' require 'io/nonblock' require 'digest/sha1' require 'fileutils' $-w = true require 'kgio' module LibReadWriteTest RANDOM_BLOB = File.open("/dev/urandom") do |fp| nr = 31 buf = fp.read(nr) # get roughly a 20MB block of random data (buf * (20 * 1024 * 1024 / nr)) + (buf * rand(123)) end def teardown @rd.close if defined?(@rd) && ! @rd.closed? @wr.close if defined?(@wr) && ! @wr.closed? FileUtils.remove_entry_secure(@tmpdir) if defined?(@tmpdir) end def test_write_empty assert_nil @wr.kgio_write("") end def test_trywrite_empty assert_nil @wr.kgio_trywrite("") end def test_writev_empty assert_nil @wr.kgio_writev([]) end def test_trywritev_empty assert_nil @wr.kgio_trywritev([]) end def test_read_zero assert_equal "", @rd.kgio_read(0) buf = "foo" assert_equal buf.object_id, @rd.kgio_read(0, buf).object_id assert_equal "", buf end def test_read_shared a = "." * 0x1000 b = a.dup @wr.syswrite "a" assert_equal "a", @rd.kgio_read(0x1000, a) assert_equal "a", a assert_equal "." * 0x1000, b end def test_read_shared_2 a = "." * 0x1000 b = a.dup @wr.syswrite "a" assert_equal "a", @rd.kgio_read(0x1000, b) assert_equal "a", b assert_equal "." * 0x1000, a end def test_tryread_zero assert_equal "", @rd.kgio_tryread(0) buf = "foo" assert_equal buf.object_id, @rd.kgio_tryread(0, buf).object_id assert_equal "", buf end def test_tryread_shared a = "." * 0x1000 b = a.dup @wr.syswrite("a") IO.select([@rd]) # this seems needed on FreeBSD 9.0 assert_equal "a", @rd.kgio_tryread(0x1000, b) assert_equal "a", b assert_equal "." * 0x1000, a end def test_tryread_shared_2 a = "." * 0x1000 b = a.dup @wr.syswrite("a") IO.select([@rd]) # this seems needed on FreeBSD 9.0 assert_equal "a", @rd.kgio_tryread(0x1000, a) assert_equal "a", a assert_equal "." * 0x1000, b end def test_read_eof @wr.close assert_nil @rd.kgio_read(5) end def test_read_bang_eof @wr.close begin @rd.kgio_read!(5) assert false, "should never get here (line:#{__LINE__})" rescue EOFError => e assert_equal [], e.backtrace end end def test_tryread_eof @wr.close IO.select([@rd]) # this seems needed on FreeBSD 9.0 assert_nil @rd.kgio_tryread(5) end def test_write_closed @rd.close begin loop { @wr.kgio_write "HI" } rescue Errno::EPIPE, Errno::ECONNRESET => e assert_equal [], e.backtrace return end assert false, "should never get here (line:#{__LINE__})" end def test_trywrite_closed @rd.close begin loop { @wr.kgio_trywrite "HI" } rescue Errno::EPIPE, Errno::ECONNRESET => e assert_equal [], e.backtrace return end assert false, "should never get here (line:#{__LINE__})" end def test_writev_closed @rd.close begin loop { @wr.kgio_writev ["HI"] } rescue Errno::EPIPE, Errno::ECONNRESET => e assert_equal [], e.backtrace return end assert false, "should never get here (line:#{__LINE__})" end def test_trywritev_closed @rd.close begin loop { @wr.kgio_trywritev ["HI"] } rescue Errno::EPIPE, Errno::ECONNRESET => e assert_equal [], e.backtrace return end assert false, "should never get here (line:#{__LINE__})" end def test_trywrite_full buf = "\302\251" * 1024 * 1024 buf2 = "" dig = Digest::SHA1.new t = Thread.new do sleep 1 nr = 0 begin dig.update(@rd.readpartial(4096, buf2)) nr += buf2.size rescue EOFError break rescue => e end while true dig.hexdigest end 50.times do wr = buf begin rv = @wr.kgio_trywrite(wr) case rv when String wr = rv when :wait_readable assert false, "should never get here line=#{__LINE__}" when :wait_writable IO.select(nil, [ @wr ]) else wr = false end end while wr end @wr.close t.join assert_equal '8ff79d8115f9fe38d18be858c66aa08a1cc27a66', t.value end def test_trywritev_full buf = ["\302\251" * 128] * 8 * 1024 buf2 = "" dig = Digest::SHA1.new t = Thread.new do sleep 1 nr = 0 begin dig.update(@rd.readpartial(4096, buf2)) nr += buf2.size rescue EOFError break rescue => e end while true dig.hexdigest end 50.times do wr = buf begin rv = @wr.kgio_trywritev(wr) case rv when Array wr = rv when :wait_readable assert false, "should never get here line=#{__LINE__}" when :wait_writable IO.select(nil, [ @wr ]) else wr = false end end while wr end @wr.close t.join assert_equal '8ff79d8115f9fe38d18be858c66aa08a1cc27a66', t.value end def test_write_conv assert_equal nil, @wr.kgio_write(10) assert_equal "10", @rd.kgio_read(2) end def test_trywrite_conv assert_equal nil, @wr.kgio_trywrite(10) IO.select([@rd]) # this seems needed on FreeBSD 9.0 assert_equal "10", @rd.kgio_tryread(2) end def test_tryread_empty assert_equal :wait_readable, @rd.kgio_tryread(1) end def test_read_too_much assert_equal nil, @wr.kgio_write("hi") assert_equal "hi", @rd.kgio_read(4) end def test_tryread_too_much assert_equal nil, @wr.kgio_trywrite("hi") assert_equal @rd, @rd.kgio_wait_readable assert_equal "hi", @rd.kgio_tryread(4) end def test_read_short assert_equal nil, @wr.kgio_write("hi") assert_equal "h", @rd.kgio_read(1) assert_equal "i", @rd.kgio_read(1) end def test_tryread_short assert_equal nil, @wr.kgio_trywrite("hi") IO.select([@rd]) # this seems needed on FreeBSD 9.0 assert_equal "h", @rd.kgio_tryread(1) assert_equal "i", @rd.kgio_tryread(1) end def test_read_extra_buf tmp = "" tmp_object_id = tmp.object_id assert_equal nil, @wr.kgio_write("hi") rv = @rd.kgio_read(2, tmp) assert_equal "hi", rv assert_equal rv.object_id, tmp.object_id assert_equal tmp_object_id, rv.object_id end def test_trywrite_return_wait_writable tmp = [] tmp << @wr.kgio_trywrite("HI") until tmp[-1] == :wait_writable assert :wait_writable === tmp[-1] assert(!(:wait_readable === tmp[-1])) assert_equal :wait_writable, tmp.pop assert tmp.size > 0 penultimate = tmp.pop assert(penultimate == "I" || penultimate == nil) assert tmp.size > 0 tmp.each { |count| assert_equal nil, count } end def test_trywritev_return_wait_writable tmp = [] tmp << @wr.kgio_trywritev(["HI"]) until tmp[-1] == :wait_writable assert :wait_writable === tmp[-1] assert(!(:wait_readable === tmp[-1])) assert_equal :wait_writable, tmp.pop assert tmp.size > 0 penultimate = tmp.pop assert(penultimate == ["I"] || penultimate == nil, "penultimate is #{penultimate.inspect}") assert tmp.size > 0 tmp.each { |count| assert_equal nil, count } end def test_tryread_extra_buf_eagain_clears_buffer tmp = "hello world" rv = @rd.kgio_tryread(2, tmp) assert_equal :wait_readable, rv assert_equal "", tmp end def test_tryread_extra_buf_eof_clears_buffer tmp = "hello world" @wr.close IO.select([@rd]) # this seems needed on FreeBSD 9.0 assert_nil @rd.kgio_tryread(2, tmp) assert_equal "", tmp end def test_monster_trywrite buf = RANDOM_BLOB.dup rv = @wr.kgio_trywrite(buf) assert_kind_of String, rv assert rv.size < buf.size @rd.nonblock = false assert_equal(buf, @rd.read(buf.size - rv.size) + rv) end def test_monster_write buf = RANDOM_BLOB.dup thr = Thread.new { @wr.kgio_write(buf) } @rd.nonblock = false readed = @rd.read(buf.size) thr.join assert_nil thr.value assert_equal buf, readed end def test_monster_trywritev buf, start = [], 0 while start < RANDOM_BLOB.size s = RANDOM_BLOB[start, 1000] start += s.size buf << s end rv = @wr.kgio_trywritev(buf) assert_kind_of Array, rv rv = rv.join assert rv.size < RANDOM_BLOB.size @rd.nonblock = false assert_equal(RANDOM_BLOB, @rd.read(RANDOM_BLOB.size - rv.size) + rv) end def test_monster_writev buf, start = [], 0 while start < RANDOM_BLOB.size s = RANDOM_BLOB[start, 10000] start += s.size buf << s end thr = Thread.new { @wr.kgio_writev(buf) } @rd.nonblock = false readed = @rd.read(RANDOM_BLOB.size) thr.join assert_nil thr.value e = (RANDOM_BLOB == readed) assert e end def test_monster_write_wait_writable @wr.instance_variable_set :@nr, 0 def @wr.kgio_wait_writable @nr += 1 IO.select(nil, [self]) end buf = RANDOM_BLOB thr = Thread.new { @wr.kgio_write(buf) } Thread.pass until thr.stop? readed = @rd.read(buf.size) thr.join assert_nil thr.value assert_equal buf, readed assert @wr.instance_variable_get(:@nr) > 0 end def test_monster_writev_wait_writable @wr.instance_variable_set :@nr, 0 def @wr.kgio_wait_writable @nr += 1 IO.select(nil, [self]) end buf = [ RANDOM_BLOB, RANDOM_BLOB ] buf_size = buf.inject(0){|c, s| c + s.size} thr = Thread.new { @wr.kgio_writev(buf) } Thread.pass until thr.stop? readed = @rd.read(buf_size) thr.join assert_nil thr.value e = (buf.join == readed) assert e assert @wr.instance_variable_get(:@nr) > 0 end def test_wait_readable_ruby_default elapsed = 0 foo = nil t0 = Time.now thr = Thread.new { sleep 1; @wr.write "HELLO" } foo = @rd.kgio_read(5) elapsed = Time.now - t0 assert elapsed >= 1.0, "elapsed: #{elapsed}" assert_equal "HELLO", foo thr.join assert_equal 5, thr.value end def test_wait_writable_ruby_default buf = "." * 512 nr = 0 begin nr += @wr.write_nonblock(buf) rescue Errno::EAGAIN break end while true elapsed = 0 foo = nil t0 = Time.now thr = Thread.new { sleep 1; @rd.read(nr) } foo = @wr.kgio_write("HELLO") elapsed = Time.now - t0 assert_nil foo if @wr.stat.pipe? assert elapsed >= 1.0, "elapsed: #{elapsed}" end assert(String === foo || foo == nil) assert_kind_of String, thr.value end def test_wait_readable_method def @rd.kgio_wait_readable defined?(@z) ? raise(RuntimeError, "Hello") : @z = "HI" end foo = nil begin foo = @rd.kgio_read(5) assert false rescue RuntimeError => e assert_equal("Hello", e.message) end assert_equal "HI", @rd.instance_variable_get(:@z) assert_nil foo end def test_tryread_wait_readable_method def @rd.kgio_wait_readable raise "Hello" end assert_equal :wait_readable, @rd.kgio_tryread(5) end def test_trywrite_wait_readable_method def @wr.kgio_wait_writable raise "Hello" end buf = "." * 4096 rv = nil until rv == :wait_writable rv = @wr.kgio_trywrite(buf) end assert_equal :wait_writable, rv end def test_wait_writable_method def @wr.kgio_wait_writable defined?(@z) ? raise(RuntimeError, "Hello") : @z = "HI" end n = [] begin loop { n << @wr.kgio_write("HIHIHIHIHIHI") } assert false rescue RuntimeError => e assert_equal("Hello", e.message) end assert n.size > 0 assert_equal "HI", @wr.instance_variable_get(:@z) end end kgio-2.11.2/test/test_unix_connect.rb0000644000004100000410000000401713241032461017613 0ustar www-datawww-datarequire 'test/unit' require 'io/nonblock' $-w = true require 'kgio' require 'tempfile' require 'tmpdir' class SubSocket < Kgio::Socket attr_accessor :foo def kgio_wait_writable @foo = "waited" end end class TestKgioUnixConnect < Test::Unit::TestCase def setup @tmpdir = Dir.mktmpdir('kgio_unix_1') tmp = Tempfile.new('kgio_unix_1', @tmpdir) @path = tmp.path tmp.close! @srv = Kgio::UNIXServer.new(@path) @addr = Socket.pack_sockaddr_un(@path) end def teardown @srv.close unless @srv.closed? File.unlink(@path) FileUtils.remove_entry_secure(@tmpdir) Kgio.accept_cloexec = true end def test_unix_socket_new_invalid assert_raises(ArgumentError) { Kgio::UNIXSocket.new('*' * 1024 * 1024) } end def test_unix_socket_new sock = Kgio::UNIXSocket.new(@path) sock.respond_to?(:close_on_exec?) and assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) assert_instance_of Kgio::UNIXSocket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] assert_equal nil, sock.kgio_write("HELLO") end def test_new sock = Kgio::Socket.new(@addr) sock.respond_to?(:close_on_exec?) and assert_equal(RUBY_VERSION.to_f >= 2.0, sock.close_on_exec?) assert_instance_of Kgio::Socket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] assert_equal nil, sock.kgio_write("HELLO") end def test_start sock = Kgio::Socket.start(@addr) assert_instance_of Kgio::Socket, sock ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] assert_equal nil, sock.kgio_write("HELLO") end def test_socket_start sock = SubSocket.start(@addr) assert_nil sock.foo ready = IO.select(nil, [ sock ]) assert_equal sock, ready[1][0] assert_equal nil, sock.kgio_write("HELLO") end def test_wait_writable_set sock = SubSocket.new(@addr) assert_kind_of Kgio::Socket, sock assert_instance_of SubSocket, sock assert_equal nil, sock.kgio_write("HELLO") end end kgio-2.11.2/test/test_tcp_client_read_server_write.rb0000644000004100000410000000050113241032461023030 0ustar www-datawww-datarequire './test/lib_read_write' class TesTcpClientReadServerWrite < Test::Unit::TestCase def setup @host = ENV["TEST_HOST"] || '127.0.0.1' @srv = Kgio::TCPServer.new(@host, 0) @port = @srv.addr[1] @wr = Kgio::TCPSocket.new(@host, @port) @rd = @srv.kgio_accept end include LibReadWriteTest end kgio-2.11.2/test/test_kgio_addr.rb0000644000004100000410000000102413241032461017035 0ustar www-datawww-data# -*- encoding: binary -*- require 'test/unit' $-w = true require 'kgio' class TestKgioAddr < Test::Unit::TestCase def test_tcp addr = ENV["TEST_HOST"] || '127.0.0.1' tcp = TCPServer.new(addr, 0) port = tcp.addr[1] client = Kgio::TCPSocket.new(addr, port) accepted = tcp.accept assert ! accepted.instance_eval { defined?(@kgio_addr) } accepted.extend Kgio::SocketMethods s = accepted.kgio_addr! assert_equal addr, s assert_equal addr, accepted.instance_variable_get(:@kgio_addr) end end kgio-2.11.2/test/test_no_dns_on_tcp_connect.rb0000644000004100000410000000050213241032461021445 0ustar www-datawww-datarequire 'test/unit' $-w = true require 'kgio' class TestNoDnsOnTcpConnect < Test::Unit::TestCase def test_connect_remote assert_raises(ArgumentError) { Kgio::TCPSocket.new("example.com", 666) } end def test_connect_localhost assert_raises(ArgumentError) { Kgio::TCPSocket.new("localhost", 666) } end end kgio-2.11.2/test/test_autopush.rb0000644000004100000410000001070113241032461016764 0ustar www-datawww-datarequire 'tempfile' require 'test/unit' begin $-w = false RUBY_PLATFORM =~ /linux/ and require 'strace' rescue LoadError end $-w = true require 'kgio' class TestAutopush < Test::Unit::TestCase TCP_CORK = 3 TCP_NOPUSH = 4 def setup Kgio.autopush = false assert_equal false, Kgio.autopush? @host = ENV["TEST_HOST"] || '127.0.0.1' @srv = Kgio::TCPServer.new(@host, 0) RUBY_PLATFORM =~ /linux/ and @srv.setsockopt(Socket::IPPROTO_TCP, TCP_CORK, 1) RUBY_PLATFORM =~ /freebsd/ and @srv.setsockopt(Socket::IPPROTO_TCP, TCP_NOPUSH, 1) @port = @srv.addr[1] end def test_autopush_accessors Kgio.autopush = true opt = RUBY_PLATFORM =~ /freebsd/ ? TCP_NOPUSH : TCP_CORK s = Kgio::TCPSocket.new(@host, @port) assert_equal 0, s.getsockopt(Socket::IPPROTO_TCP, opt).unpack('i')[0] assert ! s.kgio_autopush? s.kgio_autopush = true assert s.kgio_autopush? s.kgio_write 'asdf' assert_equal :wait_readable, s.kgio_tryread(1) assert s.kgio_autopush? val = s.getsockopt(Socket::IPPROTO_TCP, opt).unpack('i')[0] assert_operator val, :>, 0, "#{opt}=#{val} (#{RUBY_PLATFORM})" end def test_autopush_true_unix Kgio.autopush = true tmp = Tempfile.new('kgio_unix') @path = tmp.path tmp.close! @srv = Kgio::UNIXServer.new(@path) @rd = Kgio::UNIXSocket.new(@path) t0 = nil if defined?(Strace) io, err = Strace.me { @wr = @srv.kgio_accept } assert_nil err rc = nil io, err = Strace.me { t0 = Time.now @wr.kgio_write "HI\n" rc = @wr.kgio_tryread 666 } assert_nil err lines = io.readlines assert lines.grep(/TCP_CORK/).empty?, lines.inspect else @wr = @srv.kgio_accept t0 = Time.now @wr.kgio_write "HI\n" rc = @wr.kgio_tryread 666 end assert_equal "HI\n", @rd.kgio_read(3) diff = Time.now - t0 assert(diff < 0.200, "nopush on UNIX sockets? diff=#{diff} > 200ms") assert_equal :wait_readable, rc ensure File.unlink(@path) rescue nil end def test_autopush_false Kgio.autopush = nil assert_equal false, Kgio.autopush? @wr = Kgio::TCPSocket.new(@host, @port) if defined?(Strace) io, err = Strace.me { @rd = @srv.kgio_accept } assert_nil err lines = io.readlines assert lines.grep(/TCP_CORK/).empty?, lines.inspect assert_equal 1, @rd.getsockopt(Socket::SOL_TCP, TCP_CORK).unpack("i")[0] else @rd = @srv.kgio_accept end rbuf = "..." t0 = Time.now @rd.kgio_write "HI\n" @wr.kgio_read(3, rbuf) diff = Time.now - t0 assert(diff >= 0.190, "nopush broken? diff=#{diff} > 200ms") assert_equal "HI\n", rbuf end def test_autopush_true Kgio.autopush = true assert_equal true, Kgio.autopush? @wr = Kgio::TCPSocket.new(@host, @port) if defined?(Strace) io, err = Strace.me { @rd = @srv.kgio_accept } assert_nil err lines = io.readlines assert_equal 1, lines.grep(/TCP_CORK/).size, lines.inspect assert_equal 1, @rd.getsockopt(Socket::SOL_TCP, TCP_CORK).unpack("i")[0] else @rd = @srv.kgio_accept end @wr.write "HI\n" rbuf = "" if defined?(Strace) io, err = Strace.me { @rd.kgio_read(3, rbuf) } assert_nil err lines = io.readlines assert lines.grep(/TCP_CORK/).empty?, lines.inspect assert_equal "HI\n", rbuf else assert_equal "HI\n", @rd.kgio_read(3, rbuf) end t0 = Time.now @rd.kgio_write "HI2U2\n" @rd.kgio_write "HOW\n" rc = false if defined?(Strace) io, err = Strace.me { rc = @rd.kgio_tryread(666) } else rc = @rd.kgio_tryread(666) end @wr.readpartial(666, rbuf) rbuf == "HI2U2\nHOW\n" or warn "rbuf=#{rbuf.inspect} looking bad?" diff = Time.now - t0 assert(diff < 0.200, "time diff=#{diff} >= 200ms") assert_equal :wait_readable, rc if defined?(Strace) assert_nil err lines = io.readlines assert_equal 2, lines.grep(/TCP_CORK/).size, lines.inspect end @wr.close @rd.close @wr = Kgio::TCPSocket.new(@host, @port) if defined?(Strace) io, err = Strace.me { @rd = @srv.kgio_accept } assert_nil err lines = io.readlines assert lines.grep(/TCP_CORK/).empty?,"optimization fail: #{lines.inspect}" assert_equal 1, @rd.getsockopt(Socket::SOL_TCP, TCP_CORK).unpack("i")[0] end end def teardown Kgio.autopush = false end end if RUBY_PLATFORM =~ /linux|freebsd/ kgio-2.11.2/HACKING0000644000004100000410000000441513241032461013545 0ustar www-datawww-data= kgio Hacker's Guide === Documentation We use the latest version of {olddoc}[https://80x24.org/olddoc/] as much as possible. Please wrap documentation at 72 characters-per-line or less (long URLs are exempt) so it is comfortably readable from terminals. When referencing mailing list posts, use https://bogomips.org/kgio-public/$MESSAGE_ID/ if possible Message-ID remains searchable even if the archive becomes unavailable. === Code Compatibility We target mainline Ruby 1.9.3 and later. All of our C code should be compatible with all reasonably modern Unices and should run on compilers supported by the versions of Ruby we target. We will NEVER support non-Free platforms under any circumstances. Our C code follows Linux kernel coding style (hard tabs, tabs are always 8 characters wide) and NOT the indentation style of Matz Ruby. == Contributing Contributions are welcome in the form of patches, pull requests, code review, testing, documentation, user support or any other feedback. The {kgio mailing list}[mailto:kgio-public@bogomips.org] is the central coordination point for all user and developer feedback and bug reports. === Submitting Patches Follow conventions already established in the code and do not exceed 80 characters per line. Inline patches (from "git format-patch -M") to the mailing list are preferred because they allow code review and comments in the reply to the patch. We will adhere to mostly the same conventions for patch submissions as git itself. See the Documentation/SubmittingPatches document distributed with git on on patch submission guidelines to follow. Just don't email the git mailing list or maintainer with kgio patches :) == Running Development Versions It is easy to install the contents of your git working directory: Via RubyGems: gmake install-gem Without RubyGems (via setup.rb): gmake install It is not at all recommended to mix a RubyGems installation with an installation done without RubyGems, however. === Tests We use GNU make to run tests in parallel for historical reasons. Users of GNU-based systems (such as GNU/Linux) usually have GNU make installed as "make" instead of "gmake". Running the entire test suite with 4 tests in parallel: gmake -j4 test Running just one unit test: gmake test/test_poll.rb kgio-2.11.2/archive/0000755000004100000410000000000013241032461014173 5ustar www-datawww-datakgio-2.11.2/archive/slrnpull.conf0000644000004100000410000000027113241032461016715 0ustar www-datawww-data# group_name max expire headers_only gmane.comp.lang.ruby.kgio.general 1000000000 1000000000 0 # usage: slrnpull -d $PWD -h news.gmane.org --no-post kgio-2.11.2/archive/.gitignore0000644000004100000410000000002613241032461016161 0ustar www-datawww-data/data /news /requests kgio-2.11.2/.gitignore0000644000004100000410000000027013241032461014541 0ustar www-datawww-data/local.mk *.o *.log *.so *.rbc /.config /InstalledFiles /doc /local.mk /test/install-* Makefile log/ pkg/ /NEWS /.manifest /GIT-VERSION-FILE /man tags TAGS /LATEST /tmp /NEWS.atom.xml kgio-2.11.2/pkg.mk0000644000004100000410000000772713241032461013701 0ustar www-datawww-dataRUBY = ruby RAKE = rake RSYNC = rsync OLDDOC = olddoc RDOC = rdoc GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @./GIT-VERSION-GEN -include GIT-VERSION-FILE -include local.mk DLEXT := $(shell $(RUBY) -rrbconfig -e 'puts RbConfig::CONFIG["DLEXT"]') RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION') RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))') lib := lib ext := $(firstword $(wildcard ext/*)) ifneq ($(ext),) ext_pfx := tmp/ext/$(RUBY_ENGINE)-$(RUBY_VERSION) ext_h := $(wildcard $(ext)/*/*.h $(ext)/*.h) ext_src := $(wildcard $(ext)/*.c $(ext_h)) ext_pfx_src := $(addprefix $(ext_pfx)/,$(ext_src)) ext_d := $(ext_pfx)/$(ext)/.d $(ext)/extconf.rb: $(wildcard $(ext)/*.h) @>> $@ $(ext_d): @mkdir -p $(@D) @> $@ $(ext_pfx)/$(ext)/%: $(ext)/% $(ext_d) install -m 644 $< $@ $(ext_pfx)/$(ext)/Makefile: $(ext)/extconf.rb $(ext_d) $(ext_h) $(RM) -f $(@D)/*.o cd $(@D) && $(RUBY) $(CURDIR)/$(ext)/extconf.rb $(EXTCONF_ARGS) ext_sfx := _ext.$(DLEXT) ext_dl := $(ext_pfx)/$(ext)/$(notdir $(ext)_ext.$(DLEXT)) $(ext_dl): $(ext_src) $(ext_pfx_src) $(ext_pfx)/$(ext)/Makefile @echo $^ == $@ $(MAKE) -C $(@D) lib := $(lib):$(ext_pfx)/$(ext) build: $(ext_dl) else build: endif pkg_extra += GIT-VERSION-FILE NEWS LATEST NEWS: GIT-VERSION-FILE .olddoc.yml $(OLDDOC) prepare LATEST: NEWS manifest: $(RM) .manifest $(MAKE) .manifest .manifest: $(pkg_extra) (git ls-files && for i in $@ $(pkg_extra); do echo $$i; done) | \ LC_ALL=C sort > $@+ cmp $@+ $@ || mv $@+ $@ $(RM) $@+ doc:: .document .olddoc.yml $(pkg_extra) $(PLACEHOLDERS) -find lib -type f -name '*.rbc' -exec rm -f '{}' ';' -find ext -type f -name '*.rbc' -exec rm -f '{}' ';' $(RM) -r doc $(RDOC) -f oldweb $(OLDDOC) merge install -m644 COPYING doc/COPYING install -m644 NEWS doc/NEWS install -m644 NEWS.atom.xml doc/NEWS.atom.xml install -m644 $(shell LC_ALL=C grep '^[A-Z]' .document) doc/ ifneq ($(VERSION),) pkggem := pkg/$(rfpackage)-$(VERSION).gem pkgtgz := pkg/$(rfpackage)-$(VERSION).tgz # ensures we're actually on the tagged $(VERSION), only used for release verify: test x"$(shell umask)" = x0022 git rev-parse --verify refs/tags/v$(VERSION)^{} git diff-index --quiet HEAD^0 test $$(git rev-parse --verify HEAD^0) = \ $$(git rev-parse --verify refs/tags/v$(VERSION)^{}) fix-perms: -git ls-tree -r HEAD | awk '/^100644 / {print $$NF}' | xargs chmod 644 -git ls-tree -r HEAD | awk '/^100755 / {print $$NF}' | xargs chmod 755 gem: $(pkggem) install-gem: $(pkggem) gem install $(CURDIR)/$< $(pkggem): manifest fix-perms gem build $(rfpackage).gemspec mkdir -p pkg mv $(@F) $@ $(pkgtgz): distdir = $(basename $@) $(pkgtgz): HEAD = v$(VERSION) $(pkgtgz): manifest fix-perms @test -n "$(distdir)" $(RM) -r $(distdir) mkdir -p $(distdir) tar cf - $$(cat .manifest) | (cd $(distdir) && tar xf -) cd pkg && tar cf - $(basename $(@F)) | gzip -9 > $(@F)+ mv $@+ $@ package: $(pkgtgz) $(pkggem) release:: verify package # push gem to RubyGems.org gem push $(pkggem) else gem install-gem: GIT-VERSION-FILE $(MAKE) $@ VERSION=$(GIT_VERSION) endif all:: check test_units := $(wildcard test/test_*.rb) test: check check: test-unit test-unit: $(test_units) $(test_units): build $(RUBY) -I $(lib) $@ $(RUBY_TEST_OPTS) # this requires GNU coreutils variants ifneq ($(RSYNC_DEST),) publish_doc: -git set-file-times $(MAKE) doc $(MAKE) doc_gz $(RSYNC) -av doc/ $(RSYNC_DEST)/ git ls-files | xargs touch endif # Create gzip variants of the same timestamp as the original so nginx # "gzip_static on" can serve the gzipped versions directly. doc_gz: docs = $(shell find doc -type f ! -regex '^.*\.gz$$') doc_gz: for i in $(docs); do \ gzip --rsyncable -9 < $$i > $$i.gz; touch -r $$i $$i.gz; done check-warnings: @(for i in $$(git ls-files '*.rb'| grep -v '^setup\.rb$$'); \ do $(RUBY) -d -W2 -c $$i; done) | grep -v '^Syntax OK$$' || : ifneq ($(PLACEHOLDERS),) $(PLACEHOLDERS): echo olddoc_placeholder > $@ endif .PHONY: all .FORCE-GIT-VERSION-FILE doc check test $(test_units) manifest .PHONY: check-warnings kgio-2.11.2/GIT-VERSION-FILE0000644000004100000410000000002513241032461014755 0ustar www-datawww-dataGIT_VERSION = 2.11.2 kgio-2.11.2/LICENSE0000644000004100000410000000136613241032461013565 0ustar www-datawww-datakgio is copyrighted Free Software by all contributors, see logs in revision control for names and email addresses of all of them. You can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation, version {2.1}[https://www.gnu.org/licenses/lgpl-2.1.txt] or (at your option) any later version. kgio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with kgio; if not, see: https://www.gnu.org/licenses/lgpl-2.1.txt kgio-2.11.2/setup.rb0000644000004100000410000010652613241032461014251 0ustar www-datawww-data# -*- encoding: binary -*- # # setup.rb # # Copyright (c) 2000-2005 Minero Aoki # # This program is free software. # You can distribute/modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # unless Enumerable.method_defined?(:map) # Ruby 1.4.6 module Enumerable alias map collect end end unless File.respond_to?(:read) # Ruby 1.6 def File.read(fname) open(fname) {|f| return f.read } end end unless Errno.const_defined?(:ENOTEMPTY) # Windows? module Errno class ENOTEMPTY # We do not raise this exception, implementation is not needed. end end end def File.binread(fname) open(fname, 'rb') {|f| return f.read } end # for corrupted Windows' stat(2) def File.dir?(path) File.directory?((path[-1,1] == '/') ? path : path + '/') end class ConfigTable include Enumerable def initialize(rbconfig) @rbconfig = rbconfig @items = [] @table = {} # options @install_prefix = nil @config_opt = nil @verbose = true @no_harm = false end attr_accessor :install_prefix attr_accessor :config_opt attr_writer :verbose def verbose? @verbose end attr_writer :no_harm def no_harm? @no_harm end def [](key) lookup(key).resolve(self) end def []=(key, val) lookup(key).set val end def names @items.map {|i| i.name } end def each(&block) @items.each(&block) end def key?(name) @table.key?(name) end def lookup(name) @table[name] or setup_rb_error "no such config item: #{name}" end def add(item) @items.push item @table[item.name] = item end def remove(name) item = lookup(name) @items.delete_if {|i| i.name == name } @table.delete_if {|name, i| i.name == name } item end def load_script(path, inst = nil) if File.file?(path) MetaConfigEnvironment.new(self, inst).instance_eval File.read(path), path end end def savefile '.config' end def load_savefile begin File.foreach(savefile()) do |line| k, v = *line.split(/=/, 2) self[k] = v.strip end rescue Errno::ENOENT setup_rb_error $!.message + "\n#{File.basename($0)} config first" end end def save @items.each {|i| i.value } File.open(savefile(), 'w') {|f| @items.each do |i| f.printf "%s=%s\n", i.name, i.value if i.value? and i.value end } end def load_standard_entries standard_entries(@rbconfig).each do |ent| add ent end end def standard_entries(rbconfig) c = rbconfig rubypath = File.join(c['bindir'], c['ruby_install_name'] + c['EXEEXT']) major = c['MAJOR'].to_i minor = c['MINOR'].to_i teeny = c['TEENY'].to_i version = "#{major}.#{minor}" # ruby ver. >= 1.4.4? newpath_p = ((major >= 2) or ((major == 1) and ((minor >= 5) or ((minor == 4) and (teeny >= 4))))) if c['rubylibdir'] # V > 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = c['rubylibdir'] librubyverarch = c['archdir'] siteruby = c['sitedir'] siterubyver = c['sitelibdir'] siterubyverarch = c['sitearchdir'] elsif newpath_p # 1.4.4 <= V <= 1.6.3 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = c['sitedir'] siterubyver = "$siteruby/#{version}" siterubyverarch = "$siterubyver/#{c['arch']}" else # V < 1.4.4 libruby = "#{c['prefix']}/lib/ruby" librubyver = "#{c['prefix']}/lib/ruby/#{version}" librubyverarch = "#{c['prefix']}/lib/ruby/#{version}/#{c['arch']}" siteruby = "#{c['prefix']}/lib/ruby/#{version}/site_ruby" siterubyver = siteruby siterubyverarch = "$siterubyver/#{c['arch']}" end parameterize = lambda {|path| path.sub(/\A#{Regexp.quote(c['prefix'])}/, '$prefix') } if arg = c['configure_args'].split.detect {|arg| /--with-make-prog=/ =~ arg } makeprog = arg.sub(/'/, '').split(/=/, 2)[1] else makeprog = 'make' end [ ExecItem.new('installdirs', 'std/site/home', 'std: install under libruby; site: install under site_ruby; home: install under $HOME')\ {|val, table| case val when 'std' table['rbdir'] = '$librubyver' table['sodir'] = '$librubyverarch' when 'site' table['rbdir'] = '$siterubyver' table['sodir'] = '$siterubyverarch' when 'home' setup_rb_error '$HOME was not set' unless ENV['HOME'] table['prefix'] = ENV['HOME'] table['rbdir'] = '$libdir/ruby' table['sodir'] = '$libdir/ruby' end }, PathItem.new('prefix', 'path', c['prefix'], 'path prefix of target environment'), PathItem.new('bindir', 'path', parameterize.call(c['bindir']), 'the directory for commands'), PathItem.new('libdir', 'path', parameterize.call(c['libdir']), 'the directory for libraries'), PathItem.new('datadir', 'path', parameterize.call(c['datadir']), 'the directory for shared data'), PathItem.new('mandir', 'path', parameterize.call(c['mandir']), 'the directory for man pages'), PathItem.new('sysconfdir', 'path', parameterize.call(c['sysconfdir']), 'the directory for system configuration files'), PathItem.new('localstatedir', 'path', parameterize.call(c['localstatedir']), 'the directory for local state data'), PathItem.new('libruby', 'path', libruby, 'the directory for ruby libraries'), PathItem.new('librubyver', 'path', librubyver, 'the directory for standard ruby libraries'), PathItem.new('librubyverarch', 'path', librubyverarch, 'the directory for standard ruby extensions'), PathItem.new('siteruby', 'path', siteruby, 'the directory for version-independent aux ruby libraries'), PathItem.new('siterubyver', 'path', siterubyver, 'the directory for aux ruby libraries'), PathItem.new('siterubyverarch', 'path', siterubyverarch, 'the directory for aux ruby binaries'), PathItem.new('rbdir', 'path', '$siterubyver', 'the directory for ruby scripts'), PathItem.new('sodir', 'path', '$siterubyverarch', 'the directory for ruby extentions'), PathItem.new('rubypath', 'path', rubypath, 'the path to set to #! line'), ProgramItem.new('rubyprog', 'name', rubypath, 'the ruby program using for installation'), ProgramItem.new('makeprog', 'name', makeprog, 'the make program to compile ruby extentions'), SelectItem.new('shebang', 'all/ruby/never', 'ruby', 'shebang line (#!) editing mode'), BoolItem.new('without-ext', 'yes/no', 'no', 'does not compile/install ruby extentions') ] end private :standard_entries def load_multipackage_entries multipackage_entries().each do |ent| add ent end end def multipackage_entries [ PackageSelectionItem.new('with', 'name,name...', '', 'ALL', 'package names that you want to install'), PackageSelectionItem.new('without', 'name,name...', '', 'NONE', 'package names that you do not want to install') ] end private :multipackage_entries ALIASES = { 'std-ruby' => 'librubyver', 'stdruby' => 'librubyver', 'rubylibdir' => 'librubyver', 'archdir' => 'librubyverarch', 'site-ruby-common' => 'siteruby', # For backward compatibility 'site-ruby' => 'siterubyver', # For backward compatibility 'bin-dir' => 'bindir', 'bin-dir' => 'bindir', 'rb-dir' => 'rbdir', 'so-dir' => 'sodir', 'data-dir' => 'datadir', 'ruby-path' => 'rubypath', 'ruby-prog' => 'rubyprog', 'ruby' => 'rubyprog', 'make-prog' => 'makeprog', 'make' => 'makeprog' } def fixup ALIASES.each do |ali, name| @table[ali] = @table[name] end @items.freeze @table.freeze @options_re = /\A--(#{@table.keys.join('|')})(?:=(.*))?\z/ end def parse_opt(opt) m = @options_re.match(opt) or setup_rb_error "config: unknown option #{opt}" m.to_a[1,2] end def dllext @rbconfig['DLEXT'] end def value_config?(name) lookup(name).value? end class Item def initialize(name, template, default, desc) @name = name.freeze @template = template @value = default @default = default @description = desc end attr_reader :name attr_reader :description attr_accessor :default alias help_default default def help_opt "--#{@name}=#{@template}" end def value? true end def value @value end def resolve(table) @value.gsub(%r<\$([^/]+)>) { table[$1] } end def set(val) @value = check(val) end private def check(val) setup_rb_error "config: --#{name} requires argument" unless val val end end class BoolItem < Item def config_type 'bool' end def help_opt "--#{@name}" end private def check(val) return 'yes' unless val case val when /\Ay(es)?\z/i, /\At(rue)?\z/i then 'yes' when /\An(o)?\z/i, /\Af(alse)\z/i then 'no' else setup_rb_error "config: --#{@name} accepts only yes/no for argument" end end end class PathItem < Item def config_type 'path' end private def check(path) setup_rb_error "config: --#{@name} requires argument" unless path path[0,1] == '$' ? path : File.expand_path(path) end end class ProgramItem < Item def config_type 'program' end end class SelectItem < Item def initialize(name, selection, default, desc) super @ok = selection.split('/') end def config_type 'select' end private def check(val) unless @ok.include?(val.strip) setup_rb_error "config: use --#{@name}=#{@template} (#{val})" end val.strip end end class ExecItem < Item def initialize(name, selection, desc, &block) super name, selection, nil, desc @ok = selection.split('/') @action = block end def config_type 'exec' end def value? false end def resolve(table) setup_rb_error "$#{name()} wrongly used as option value" end undef set def evaluate(val, table) v = val.strip.downcase unless @ok.include?(v) setup_rb_error "invalid option --#{@name}=#{val} (use #{@template})" end @action.call v, table end end class PackageSelectionItem < Item def initialize(name, template, default, help_default, desc) super name, template, default, desc @help_default = help_default end attr_reader :help_default def config_type 'package' end private def check(val) unless File.dir?("packages/#{val}") setup_rb_error "config: no such package: #{val}" end val end end class MetaConfigEnvironment def initialize(config, installer) @config = config @installer = installer end def config_names @config.names end def config?(name) @config.key?(name) end def bool_config?(name) @config.lookup(name).config_type == 'bool' end def path_config?(name) @config.lookup(name).config_type == 'path' end def value_config?(name) @config.lookup(name).config_type != 'exec' end def add_config(item) @config.add item end def add_bool_config(name, default, desc) @config.add BoolItem.new(name, 'yes/no', default ? 'yes' : 'no', desc) end def add_path_config(name, default, desc) @config.add PathItem.new(name, 'path', default, desc) end def set_config_default(name, default) @config.lookup(name).default = default end def remove_config(name) @config.remove(name) end # For only multipackage def packages raise '[setup.rb fatal] multi-package metaconfig API packages() called for single-package; contact application package vendor' unless @installer @installer.packages end # For only multipackage def declare_packages(list) raise '[setup.rb fatal] multi-package metaconfig API declare_packages() called for single-package; contact application package vendor' unless @installer @installer.packages = list end end end # class ConfigTable # This module requires: #verbose?, #no_harm? module FileOperations def mkdir_p(dirname, prefix = nil) dirname = prefix + File.expand_path(dirname) if prefix $stderr.puts "mkdir -p #{dirname}" if verbose? return if no_harm? # Does not check '/', it's too abnormal. dirs = File.expand_path(dirname).split(%r<(?=/)>) if /\A[a-z]:\z/i =~ dirs[0] disk = dirs.shift dirs[0] = disk + dirs[0] end dirs.each_index do |idx| path = dirs[0..idx].join('') Dir.mkdir path unless File.dir?(path) end end def rm_f(path) $stderr.puts "rm -f #{path}" if verbose? return if no_harm? force_remove_file path end def rm_rf(path) $stderr.puts "rm -rf #{path}" if verbose? return if no_harm? remove_tree path end def remove_tree(path) if File.symlink?(path) remove_file path elsif File.dir?(path) remove_tree0 path else force_remove_file path end end def remove_tree0(path) Dir.foreach(path) do |ent| next if ent == '.' next if ent == '..' entpath = "#{path}/#{ent}" if File.symlink?(entpath) remove_file entpath elsif File.dir?(entpath) remove_tree0 entpath else force_remove_file entpath end end begin Dir.rmdir path rescue Errno::ENOTEMPTY # directory may not be empty end end def move_file(src, dest) force_remove_file dest begin File.rename src, dest rescue File.open(dest, 'wb') {|f| f.write File.binread(src) } File.chmod File.stat(src).mode, dest File.unlink src end end def force_remove_file(path) begin remove_file path rescue end end def remove_file(path) File.chmod 0777, path File.unlink path end def install(from, dest, mode, prefix = nil) $stderr.puts "install #{from} #{dest}" if verbose? return if no_harm? realdest = prefix ? prefix + File.expand_path(dest) : dest realdest = File.join(realdest, File.basename(from)) if File.dir?(realdest) str = File.binread(from) if diff?(str, realdest) verbose_off { rm_f realdest if File.exist?(realdest) } File.open(realdest, 'wb') {|f| f.write str } File.chmod mode, realdest File.open("#{objdir_root()}/InstalledFiles", 'a') {|f| if prefix f.puts realdest.sub(prefix, '') else f.puts realdest end } end end def diff?(new_content, path) return true unless File.exist?(path) new_content != File.binread(path) end def command(*args) $stderr.puts args.join(' ') if verbose? system(*args) or raise RuntimeError, "system(#{args.map{|a| a.inspect }.join(' ')}) failed" end def ruby(*args) command config('rubyprog'), *args end def make(task = nil) command(*[config('makeprog'), task].compact) end def extdir?(dir) File.exist?("#{dir}/MANIFEST") or File.exist?("#{dir}/extconf.rb") end def files_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.file?("#{dir}/#{ent}") } } end DIR_REJECT = %w( . .. CVS SCCS RCS CVS.adm .svn ) def directories_of(dir) Dir.open(dir) {|d| return d.select {|ent| File.dir?("#{dir}/#{ent}") } - DIR_REJECT } end end # This module requires: #srcdir_root, #objdir_root, #relpath module HookScriptAPI def get_config(key) @config[key] end alias config get_config # obsolete: use metaconfig to change configuration def set_config(key, val) @config[key] = val end # # srcdir/objdir (works only in the package directory) # def curr_srcdir "#{srcdir_root()}/#{relpath()}" end def curr_objdir "#{objdir_root()}/#{relpath()}" end def srcfile(path) "#{curr_srcdir()}/#{path}" end def srcexist?(path) File.exist?(srcfile(path)) end def srcdirectory?(path) File.dir?(srcfile(path)) end def srcfile?(path) File.file?(srcfile(path)) end def srcentries(path = '.') Dir.open("#{curr_srcdir()}/#{path}") {|d| return d.to_a - %w(. ..) } end def srcfiles(path = '.') srcentries(path).select {|fname| File.file?(File.join(curr_srcdir(), path, fname)) } end def srcdirectories(path = '.') srcentries(path).select {|fname| File.dir?(File.join(curr_srcdir(), path, fname)) } end end class ToplevelInstaller Version = '3.4.1' Copyright = 'Copyright (c) 2000-2005 Minero Aoki' TASKS = [ [ 'all', 'do config, setup, then install' ], [ 'config', 'saves your configurations' ], [ 'show', 'shows current configuration' ], [ 'setup', 'compiles ruby extentions and others' ], [ 'install', 'installs files' ], [ 'test', 'run all tests in test/' ], [ 'clean', "does `make clean' for each extention" ], [ 'distclean',"does `make distclean' for each extention" ] ] def ToplevelInstaller.invoke config = ConfigTable.new(load_rbconfig()) config.load_standard_entries config.load_multipackage_entries if multipackage? config.fixup klass = (multipackage?() ? ToplevelInstallerMulti : ToplevelInstaller) klass.new(File.dirname($0), config).invoke end def ToplevelInstaller.multipackage? File.dir?(File.dirname($0) + '/packages') end def ToplevelInstaller.load_rbconfig if arg = ARGV.detect {|arg| /\A--rbconfig=/ =~ arg } ARGV.delete(arg) load File.expand_path(arg.split(/=/, 2)[1]) $".push 'rbconfig.rb' else require 'rbconfig' end ::Config::CONFIG end def initialize(ardir_root, config) @ardir = File.expand_path(ardir_root) @config = config # cache @valid_task_re = nil end def config(key) @config[key] end def inspect "#<#{self.class} #{__id__()}>" end def invoke run_metaconfigs case task = parsearg_global() when nil, 'all' parsearg_config init_installers exec_config exec_setup exec_install else case task when 'config', 'test' ; when 'clean', 'distclean' @config.load_savefile if File.exist?(@config.savefile) else @config.load_savefile end __send__ "parsearg_#{task}" init_installers __send__ "exec_#{task}" end end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig" end def init_installers @installer = Installer.new(@config, @ardir, File.expand_path('.')) end # # Hook Script API bases # def srcdir_root @ardir end def objdir_root '.' end def relpath '.' end # # Option Parsing # def parsearg_global while arg = ARGV.shift case arg when /\A\w+\z/ setup_rb_error "invalid task: #{arg}" unless valid_task?(arg) return arg when '-q', '--quiet' @config.verbose = false when '--verbose' @config.verbose = true when '--help' print_usage $stdout exit 0 when '--version' puts "#{File.basename($0)} version #{Version}" exit 0 when '--copyright' puts Copyright exit 0 else setup_rb_error "unknown global option '#{arg}'" end end nil end def valid_task?(t) valid_task_re() =~ t end def valid_task_re @valid_task_re ||= /\A(?:#{TASKS.map {|task,desc| task }.join('|')})\z/ end def parsearg_no_options unless ARGV.empty? task = caller(0).first.slice(%r<`parsearg_(\w+)'>, 1) setup_rb_error "#{task}: unknown options: #{ARGV.join(' ')}" end end alias parsearg_show parsearg_no_options alias parsearg_setup parsearg_no_options alias parsearg_test parsearg_no_options alias parsearg_clean parsearg_no_options alias parsearg_distclean parsearg_no_options def parsearg_config evalopt = [] set = [] @config.config_opt = [] while i = ARGV.shift if /\A--?\z/ =~ i @config.config_opt = ARGV.dup break end name, value = *@config.parse_opt(i) if @config.value_config?(name) @config[name] = value else evalopt.push [name, value] end set.push name end evalopt.each do |name, value| @config.lookup(name).evaluate value, @config end # Check if configuration is valid set.each do |n| @config[n] if @config.value_config?(n) end end def parsearg_install @config.no_harm = false @config.install_prefix = '' while a = ARGV.shift case a when '--no-harm' @config.no_harm = true when /\A--prefix=/ path = a.split(/=/, 2)[1] path = File.expand_path(path) unless path[0,1] == '/' @config.install_prefix = path else setup_rb_error "install: unknown option #{a}" end end end def print_usage(out) out.puts 'Typical Installation Procedure:' out.puts " $ ruby #{File.basename $0} config" out.puts " $ ruby #{File.basename $0} setup" out.puts " # ruby #{File.basename $0} install (may require root privilege)" out.puts out.puts 'Detailed Usage:' out.puts " ruby #{File.basename $0} " out.puts " ruby #{File.basename $0} [] []" fmt = " %-24s %s\n" out.puts out.puts 'Global options:' out.printf fmt, '-q,--quiet', 'suppress message outputs' out.printf fmt, ' --verbose', 'output messages verbosely' out.printf fmt, ' --help', 'print this message' out.printf fmt, ' --version', 'print version and quit' out.printf fmt, ' --copyright', 'print copyright and quit' out.puts out.puts 'Tasks:' TASKS.each do |name, desc| out.printf fmt, name, desc end fmt = " %-24s %s [%s]\n" out.puts out.puts 'Options for CONFIG or ALL:' @config.each do |item| out.printf fmt, item.help_opt, item.description, item.help_default end out.printf fmt, '--rbconfig=path', 'rbconfig.rb to load',"running ruby's" out.puts out.puts 'Options for INSTALL:' out.printf fmt, '--no-harm', 'only display what to do if given', 'off' out.printf fmt, '--prefix=path', 'install path prefix', '' out.puts end # # Task Handlers # def exec_config @installer.exec_config @config.save # must be final end def exec_setup @installer.exec_setup end def exec_install @installer.exec_install end def exec_test @installer.exec_test end def exec_show @config.each do |i| printf "%-20s %s\n", i.name, i.value if i.value? end end def exec_clean @installer.exec_clean end def exec_distclean @installer.exec_distclean end end # class ToplevelInstaller class ToplevelInstallerMulti < ToplevelInstaller include FileOperations def initialize(ardir_root, config) super @packages = directories_of("#{@ardir}/packages") raise 'no package exists' if @packages.empty? @root_installer = Installer.new(@config, @ardir, File.expand_path('.')) end def run_metaconfigs @config.load_script "#{@ardir}/metaconfig", self @packages.each do |name| @config.load_script "#{@ardir}/packages/#{name}/metaconfig" end end attr_reader :packages def packages=(list) raise 'package list is empty' if list.empty? list.each do |name| raise "directory packages/#{name} does not exist"\ unless File.dir?("#{@ardir}/packages/#{name}") end @packages = list end def init_installers @installers = {} @packages.each do |pack| @installers[pack] = Installer.new(@config, "#{@ardir}/packages/#{pack}", "packages/#{pack}") end with = extract_selection(config('with')) without = extract_selection(config('without')) @selected = @installers.keys.select {|name| (with.empty? or with.include?(name)) \ and not without.include?(name) } end def extract_selection(list) a = list.split(/,/) a.each do |name| setup_rb_error "no such package: #{name}" unless @installers.key?(name) end a end def print_usage(f) super f.puts 'Inluded packages:' f.puts ' ' + @packages.sort.join(' ') f.puts end # # Task Handlers # def exec_config run_hook 'pre-config' each_selected_installers {|inst| inst.exec_config } run_hook 'post-config' @config.save # must be final end def exec_setup run_hook 'pre-setup' each_selected_installers {|inst| inst.exec_setup } run_hook 'post-setup' end def exec_install run_hook 'pre-install' each_selected_installers {|inst| inst.exec_install } run_hook 'post-install' end def exec_test run_hook 'pre-test' each_selected_installers {|inst| inst.exec_test } run_hook 'post-test' end def exec_clean rm_f @config.savefile run_hook 'pre-clean' each_selected_installers {|inst| inst.exec_clean } run_hook 'post-clean' end def exec_distclean rm_f @config.savefile run_hook 'pre-distclean' each_selected_installers {|inst| inst.exec_distclean } run_hook 'post-distclean' end # # lib # def each_selected_installers Dir.mkdir 'packages' unless File.dir?('packages') @selected.each do |pack| $stderr.puts "Processing the package `#{pack}' ..." if verbose? Dir.mkdir "packages/#{pack}" unless File.dir?("packages/#{pack}") Dir.chdir "packages/#{pack}" yield @installers[pack] Dir.chdir '../..' end end def run_hook(id) @root_installer.run_hook id end # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end end # class ToplevelInstallerMulti class Installer FILETYPES = %w( bin lib ext data conf man ) include FileOperations include HookScriptAPI def initialize(config, srcroot, objroot) @config = config @srcdir = File.expand_path(srcroot) @objdir = File.expand_path(objroot) @currdir = '.' end def inspect "#<#{self.class} #{File.basename(@srcdir)}>" end def noop(rel) end # # Hook Script API base methods # def srcdir_root @srcdir end def objdir_root @objdir end def relpath @currdir end # # Config Access # # module FileOperations requires this def verbose? @config.verbose? end # module FileOperations requires this def no_harm? @config.no_harm? end def verbose_off begin save, @config.verbose = @config.verbose?, false yield ensure @config.verbose = save end end # # TASK config # def exec_config exec_task_traverse 'config' end alias config_dir_bin noop alias config_dir_lib noop def config_dir_ext(rel) extconf if extdir?(curr_srcdir()) end alias config_dir_data noop alias config_dir_conf noop alias config_dir_man noop def extconf ruby "#{curr_srcdir()}/extconf.rb", *@config.config_opt end # # TASK setup # def exec_setup exec_task_traverse 'setup' end def setup_dir_bin(rel) files_of(curr_srcdir()).each do |fname| update_shebang_line "#{curr_srcdir()}/#{fname}" end end alias setup_dir_lib noop def setup_dir_ext(rel) make if extdir?(curr_srcdir()) end alias setup_dir_data noop alias setup_dir_conf noop alias setup_dir_man noop def update_shebang_line(path) return if no_harm? return if config('shebang') == 'never' old = Shebang.load(path) if old $stderr.puts "warning: #{path}: Shebang line includes too many args. It is not portable and your program may not work." if old.args.size > 1 new = new_shebang(old) return if new.to_s == old.to_s else return unless config('shebang') == 'all' new = Shebang.new(config('rubypath')) end $stderr.puts "updating shebang: #{File.basename(path)}" if verbose? open_atomic_writer(path) {|output| File.open(path, 'rb') {|f| f.gets if old # discard output.puts new.to_s output.print f.read } } end def new_shebang(old) if /\Aruby/ =~ File.basename(old.cmd) Shebang.new(config('rubypath'), old.args) elsif File.basename(old.cmd) == 'env' and old.args.first == 'ruby' Shebang.new(config('rubypath'), old.args[1..-1]) else return old unless config('shebang') == 'all' Shebang.new(config('rubypath')) end end def open_atomic_writer(path, &block) tmpfile = File.basename(path) + '.tmp' begin File.open(tmpfile, 'wb', &block) File.rename tmpfile, File.basename(path) ensure File.unlink tmpfile if File.exist?(tmpfile) end end class Shebang def Shebang.load(path) line = nil File.open(path) {|f| line = f.gets } return nil unless /\A#!/ =~ line parse(line) end def Shebang.parse(line) cmd, *args = *line.strip.sub(/\A\#!/, '').split(' ') new(cmd, args) end def initialize(cmd, args = []) @cmd = cmd @args = args end attr_reader :cmd attr_reader :args def to_s "#! #{@cmd}" + (@args.empty? ? '' : " #{@args.join(' ')}") end end # # TASK install # def exec_install rm_f 'InstalledFiles' exec_task_traverse 'install' end def install_dir_bin(rel) install_files targetfiles(), "#{config('bindir')}/#{rel}", 0755 end def install_dir_lib(rel) install_files libfiles(), "#{config('rbdir')}/#{rel}", 0644 end def install_dir_ext(rel) return unless extdir?(curr_srcdir()) install_files rubyextentions('.'), "#{config('sodir')}/#{File.dirname(rel)}", 0555 end def install_dir_data(rel) install_files targetfiles(), "#{config('datadir')}/#{rel}", 0644 end def install_dir_conf(rel) # FIXME: should not remove current config files # (rename previous file to .old/.org) install_files targetfiles(), "#{config('sysconfdir')}/#{rel}", 0644 end def install_dir_man(rel) install_files targetfiles(), "#{config('mandir')}/#{rel}", 0644 end def install_files(list, dest, mode) mkdir_p dest, @config.install_prefix list.each do |fname| install fname, dest, mode, @config.install_prefix end end def libfiles glob_reject(%w(*.y *.output), targetfiles()) end def rubyextentions(dir) ents = glob_select("*.#{@config.dllext}", targetfiles()) if ents.empty? setup_rb_error "no ruby extention exists: 'ruby #{$0} setup' first" end ents end def targetfiles mapdir(existfiles() - hookfiles()) end def mapdir(ents) ents.map {|ent| if File.exist?(ent) then ent # objdir else "#{curr_srcdir()}/#{ent}" # srcdir end } end # picked up many entries from cvs-1.11.1/src/ignore.c JUNK_FILES = %w( core RCSLOG tags TAGS .make.state .nse_depinfo #* .#* cvslog.* ,* .del-* *.olb *~ *.old *.bak *.BAK *.orig *.rej _$* *$ *.org *.in .* ) def existfiles glob_reject(JUNK_FILES, (files_of(curr_srcdir()) | files_of('.'))) end def hookfiles %w( pre-%s post-%s pre-%s.rb post-%s.rb ).map {|fmt| %w( config setup install clean ).map {|t| sprintf(fmt, t) } }.flatten end def glob_select(pat, ents) re = globs2re([pat]) ents.select {|ent| re =~ ent } end def glob_reject(pats, ents) re = globs2re(pats) ents.reject {|ent| re =~ ent } end GLOB2REGEX = { '.' => '\.', '$' => '\$', '#' => '\#', '*' => '.*' } def globs2re(pats) /\A(?:#{ pats.map {|pat| pat.gsub(/[\.\$\#\*]/) {|ch| GLOB2REGEX[ch] } }.join('|') })\z/ end # # TASK test # TESTDIR = 'test' def exec_test unless File.directory?('test') $stderr.puts 'no test in this package' if verbose? return end $stderr.puts 'Running tests...' if verbose? begin require 'test/unit' rescue LoadError setup_rb_error 'test/unit cannot loaded. You need Ruby 1.8 or later to invoke this task.' end runner = Test::Unit::AutoRunner.new(true) runner.to_run << TESTDIR runner.run end # # TASK clean # def exec_clean exec_task_traverse 'clean' rm_f @config.savefile rm_f 'InstalledFiles' end alias clean_dir_bin noop alias clean_dir_lib noop alias clean_dir_data noop alias clean_dir_conf noop alias clean_dir_man noop def clean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'clean' if File.file?('Makefile') end # # TASK distclean # def exec_distclean exec_task_traverse 'distclean' rm_f @config.savefile rm_f 'InstalledFiles' end alias distclean_dir_bin noop alias distclean_dir_lib noop def distclean_dir_ext(rel) return unless extdir?(curr_srcdir()) make 'distclean' if File.file?('Makefile') end alias distclean_dir_data noop alias distclean_dir_conf noop alias distclean_dir_man noop # # Traversing # def exec_task_traverse(task) run_hook "pre-#{task}" FILETYPES.each do |type| if type == 'ext' and config('without-ext') == 'yes' $stderr.puts 'skipping ext/* by user option' if verbose? next end traverse task, type, "#{task}_dir_#{type}" end run_hook "post-#{task}" end def traverse(task, rel, mid) dive_into(rel) { run_hook "pre-#{task}" __send__ mid, rel.sub(%r[\A.*?(?:/|\z)], '') directories_of(curr_srcdir()).each do |d| traverse task, "#{rel}/#{d}", mid end run_hook "post-#{task}" } end def dive_into(rel) return unless File.dir?("#{@srcdir}/#{rel}") dir = File.basename(rel) Dir.mkdir dir unless File.dir?(dir) prevdir = Dir.pwd Dir.chdir dir $stderr.puts '---> ' + rel if verbose? @currdir = rel yield Dir.chdir prevdir $stderr.puts '<--- ' + rel if verbose? @currdir = File.dirname(rel) end def run_hook(id) path = [ "#{curr_srcdir()}/#{id}", "#{curr_srcdir()}/#{id}.rb" ].detect {|cand| File.file?(cand) } return unless path begin instance_eval File.read(path), path, 1 rescue raise if $DEBUG setup_rb_error "hook #{path} failed:\n" + $!.message end end end # class Installer class SetupError < StandardError; end def setup_rb_error(msg) raise SetupError, msg end if $0 == __FILE__ begin ToplevelInstaller.invoke rescue SetupError raise if $DEBUG $stderr.puts $!.message $stderr.puts "Try 'ruby #{$0} --help' for detailed usage." exit 1 end end kgio-2.11.2/TODO0000644000004100000410000000021313241032461013236 0ustar www-datawww-data* obsolete kgio by improving *_nonblock methods in Ruby itself (Done for *_nonblock in Ruby 2.3.x, not sure for poll and writev support) kgio-2.11.2/ext/0000755000004100000410000000000013241032461013352 5ustar www-datawww-datakgio-2.11.2/ext/kgio/0000755000004100000410000000000013241032461014303 5ustar www-datawww-datakgio-2.11.2/ext/kgio/kgio.h0000644000004100000410000000605113241032461015407 0ustar www-datawww-data#ifndef KGIO_H #define KGIO_H #include #ifdef HAVE_RUBY_IO_H # include #else # include #endif #ifdef HAVE_RUBY_THREAD_H # include #endif #include #include #include #include #include #include #include #include #include #include #include "ancient_ruby.h" void init_kgio_wait(void); void init_kgio_read(void); void init_kgio_write(void); void init_kgio_writev(void); void init_kgio_accept(void); void init_kgio_connect(void); void init_kgio_autopush(void); void init_kgio_poll(void); void init_kgio_tryopen(void); void kgio_autopush_accept(VALUE, VALUE); void kgio_autopush_recv(VALUE); void kgio_autopush_send(VALUE); VALUE kgio_call_wait_writable(VALUE io); VALUE kgio_call_wait_readable(VALUE io); #if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && defined(HAVE_RUBY_THREAD_H) # define KGIO_WITHOUT_GVL(fn,data1,ubf,data2) \ rb_thread_call_without_gvl((fn),(data1),(ubf),(data2)) #elif defined(HAVE_RB_THREAD_BLOCKING_REGION) typedef VALUE(*kgio_blocking_fn_t)(void*); # define KGIO_WITHOUT_GVL(fn,data1,ubf,data2) \ rb_thread_blocking_region((kgio_blocking_fn_t)(fn),(data1),(ubf),(data2)) #endif /* HAVE_RB_THREAD_CALL_WITHOUT_GVL || HAVE_RB_THREAD_BLOCKING_REGION */ #if defined(KGIO_WITHOUT_GVL) && defined(HAVE_POLL) # define USE_KGIO_POLL #endif /* USE_KGIO_POLL */ #ifndef HAVE_RB_UPDATE_MAX_FD # define rb_update_max_fd(fd) for (;0;) #endif /* * 2012/12/13 - Linux 3.7 was released on 2012/12/10 with TFO. * Headers distributed with glibc will take some time to catch up and * be officially released. Most GNU/Linux distros will take a few months * to a year longer. "Enterprise" distros will probably take 5-7 years. * So keep these until 2017 at least... */ #ifdef __linux__ # ifndef MSG_FASTOPEN # define MSG_FASTOPEN 0x20000000 /* for clients */ # endif # ifndef TCP_FASTOPEN # define TCP_FASTOPEN 23 /* for listeners */ # endif /* we _may_ have TFO support */ # define KGIO_TFO_MAYBE (1) #else /* rely entirely on standard system headers */ # define KGIO_TFO_MAYBE (0) #endif extern unsigned kgio_tfo; NORETURN(void kgio_raise_empty_bt(VALUE, const char *)); NORETURN(void kgio_wr_sys_fail(const char *)); NORETURN(void kgio_rd_sys_fail(const char *)); /* * we know MSG_DONTWAIT works properly on all stream sockets under Linux * we can define this macro for other platforms as people care and * notice. */ # if defined(__linux__) # define USE_MSG_DONTWAIT # endif #ifdef USE_MSG_DONTWAIT /* we don't need these variants, we call kgio_autopush_send/recv directly */ static inline void kgio_autopush_write(VALUE io) { } #else static inline void kgio_autopush_write(VALUE io) { kgio_autopush_send(io); } #endif /* prefer rb_str_subseq because we don't use negative offsets */ #ifndef HAVE_RB_STR_SUBSEQ #define MY_STR_SUBSEQ(str,beg,len) rb_str_substr((str),(beg),(len)) #else #define MY_STR_SUBSEQ(str,beg,len) rb_str_subseq((str),(beg),(len)) #endif #endif /* KGIO_H */ kgio-2.11.2/ext/kgio/my_fileno.h0000644000004100000410000000126313241032461016437 0ustar www-datawww-data#include #ifdef HAVE_RUBY_IO_H # include #else # include # include #endif #if ! HAVE_RB_IO_T # define rb_io_t OpenFile #endif #ifdef GetReadFile # define FPTR_TO_FD(fptr) (fileno(GetReadFile(fptr))) #else # if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8) # define FPTR_TO_FD(fptr) fileno(fptr->f) # else # define FPTR_TO_FD(fptr) fptr->fd # endif #endif static int my_fileno(VALUE io) { rb_io_t *fptr; int fd; if (TYPE(io) != T_FILE) io = rb_convert_type(io, T_FILE, "IO", "to_io"); GetOpenFile(io, fptr); fd = FPTR_TO_FD(fptr); if (fd < 0) rb_raise(rb_eIOError, "closed stream"); return fd; } kgio-2.11.2/ext/kgio/ancient_ruby.h0000644000004100000410000000116113241032461017135 0ustar www-datawww-data#ifndef MISSING_ANCIENT_RUBY_H #define MISSING_ANCIENT_RUBY_H #ifndef HAVE_RB_STR_SET_LEN static void my_str_set_len(VALUE str, long len) { RSTRING(str)->len = len; RSTRING(str)->ptr[len] = '\0'; } #define rb_str_set_len(str,len) my_str_set_len((str),(len)) #endif /* ! HAVE_RB_STR_SET_LEN */ #ifndef RSTRING_PTR # define RSTRING_PTR(s) (RSTRING(s)->ptr) #endif /* !defined(RSTRING_PTR) */ #ifndef RSTRING_LEN # define RSTRING_LEN(s) (RSTRING(s)->len) #endif /* !defined(RSTRING_LEN) */ #ifndef RARRAY_LEN # define RARRAY_LEN(s) (RARRAY(s)->len) #endif /* !defined(RARRAY_LEN) */ #endif /* MISSING_ANCIENT_RUBY_H */ kgio-2.11.2/ext/kgio/writev.c0000644000004100000410000002026613241032461015775 0ustar www-datawww-data/* * we're currently too lazy to use rb_ensure to free an allocation, so we * the abuse rb_str_* API for a temporary buffer */ #define RSTRING_MODIFIED 1 #include "kgio.h" #include "my_fileno.h" #include "nonblock.h" #ifdef HAVE_WRITEV # include # define USE_WRITEV 1 #else # define USE_WRITEV 0 static ssize_t assert_writev(int fd, void* iov, int len) { assert(0 && "you should not try to call writev"); return -1; } # define writev assert_writev #endif #ifndef HAVE_RB_ARY_SUBSEQ static inline VALUE my_ary_subseq(VALUE ary, long idx, long len) { VALUE args[2] = { LONG2FIX(idx), LONG2FIX(len) }; return rb_ary_aref(2, args, ary); } #define MY_ARY_SUBSEQ(ary,idx,len) my_ary_subseq((ary),(idx),(len)) #else #define MY_ARY_SUBSEQ(ary,idx,len) rb_ary_subseq((ary),(idx),(len)) #endif static VALUE sym_wait_writable; #ifndef HAVE_WRITEV #define iovec my_iovec struct my_iovec { void *iov_base; size_t iov_len; }; #endif /* tests for choosing following constants were done on Linux 3.0 x86_64 * (Ubuntu 12.04) Core i3 i3-2330M slowed to 1600MHz * testing script https://gist.github.com/2850641 * fill free to make more thorough testing and choose better value */ /* test shows that its meaningless to set WRITEV_MEMLIMIT more that 1M * even when tcp_wmem set to relatively high value (2M) (in fact, it becomes * even slower). 512K performs a bit better in average case. */ #define WRITEV_MEMLIMIT (512*1024) /* same test shows that custom_writev is faster than glibc writev when * average string is smaller than ~500 bytes and slower when average strings * is greater then ~600 bytes. 512 bytes were choosen cause current compilers * turns x/512 into x>>9 */ #define WRITEV_IMPL_THRESHOLD 512 static int iov_max = 1024; /* this could be overriden in init */ struct wrv_args { VALUE io; VALUE buf; VALUE vec_buf; /* FIXME: this requires RSTRING_MODIFY for rbx */ struct iovec *vec; int iov_cnt; size_t batch_len; int something_written; int fd; }; static ssize_t custom_writev(int fd, const struct iovec *vec, int iov_cnt, size_t total_len) { int i; ssize_t result; char *buf, *curbuf; const struct iovec *curvec = vec; /* we do not want to use ruby's xmalloc because * it can fire GC, and we'll free buffer shortly anyway */ curbuf = buf = malloc(total_len); if (buf == NULL) return -1; for (i = 0; i < iov_cnt; i++, curvec++) { memcpy(curbuf, curvec->iov_base, curvec->iov_len); curbuf += curvec->iov_len; } result = write(fd, buf, total_len); /* free() may alter errno */ i = errno; free(buf); errno = i; return result; } static void prepare_writev(struct wrv_args *a, VALUE io, VALUE ary) { a->io = io; a->fd = my_fileno(io); a->something_written = 0; if (TYPE(ary) == T_ARRAY) /* rb_ary_subseq will not copy array unless it modified */ a->buf = MY_ARY_SUBSEQ(ary, 0, RARRAY_LEN(ary)); else a->buf = rb_Array(ary); a->vec_buf = rb_str_new(0, 0); a->vec = NULL; } #ifndef RARRAY_LENINT static inline int rarray_int(VALUE val) { long num = RARRAY_LEN(val); if ((long)(int)num != num) rb_raise(rb_eRangeError, "%ld cannot to be an int", num); return (int)num; } #define RARRAY_LENINT(n) rarray_int(n) #endif static void fill_iovec(struct wrv_args *a) { int i; struct iovec *curvec; a->iov_cnt = RARRAY_LENINT(a->buf); a->batch_len = 0; if (a->iov_cnt == 0) return; if (a->iov_cnt > iov_max) a->iov_cnt = iov_max; rb_str_resize(a->vec_buf, sizeof(struct iovec) * a->iov_cnt); curvec = a->vec = (struct iovec*)RSTRING_PTR(a->vec_buf); for (i=0; i < a->iov_cnt; i++, curvec++) { VALUE str = rb_ary_entry(a->buf, i); long str_len, next_len; if (TYPE(str) != T_STRING) { str = rb_obj_as_string(str); rb_ary_store(a->buf, i, str); } str_len = RSTRING_LEN(str); /* lets limit total memory to write, * but always take first string */ next_len = a->batch_len + str_len; if (i && next_len > WRITEV_MEMLIMIT) { a->iov_cnt = i; break; } a->batch_len = next_len; curvec->iov_base = RSTRING_PTR(str); curvec->iov_len = str_len; } } static long trim_writev_buffer(struct wrv_args *a, ssize_t n) { long i; long ary_len = RARRAY_LEN(a->buf); if (n == (ssize_t)a->batch_len) { i = a->iov_cnt; n = 0; } else { for (i = 0; n && i < ary_len; i++) { VALUE entry = rb_ary_entry(a->buf, i); n -= (ssize_t)RSTRING_LEN(entry); if (n < 0) break; } } /* all done */ if (i == ary_len) { assert(n == 0 && "writev system call is broken"); a->buf = Qnil; return 0; } /* partially done, remove fully-written buffers */ if (i > 0) a->buf = MY_ARY_SUBSEQ(a->buf, i, ary_len - i); /* setup+replace partially written buffer */ if (n < 0) { VALUE str = rb_ary_entry(a->buf, 0); long str_len = RSTRING_LEN(str); str = MY_STR_SUBSEQ(str, str_len + n, -n); rb_ary_store(a->buf, 0, str); } return RARRAY_LEN(a->buf); } static long writev_check(struct wrv_args *a, ssize_t n, const char *msg, int io_wait) { if (n >= 0) { if (n > 0) a->something_written = 1; return trim_writev_buffer(a, n); } else if (n < 0) { if (errno == EINTR) { a->fd = my_fileno(a->io); return -1; } if (errno == EAGAIN) { if (io_wait) { (void)kgio_call_wait_writable(a->io); return -1; } else if (!a->something_written) { a->buf = sym_wait_writable; } return 0; } kgio_wr_sys_fail(msg); } return 0; } static VALUE my_writev(VALUE io, VALUE ary, int io_wait) { struct wrv_args a; ssize_t n; prepare_writev(&a, io, ary); set_nonblocking(a.fd); do { fill_iovec(&a); if (a.iov_cnt == 0) n = 0; else if (a.iov_cnt == 1) n = write(a.fd, a.vec[0].iov_base, a.vec[0].iov_len); /* for big strings use library function */ else if (USE_WRITEV && ((long)(a.batch_len/WRITEV_IMPL_THRESHOLD) > a.iov_cnt)) n = writev(a.fd, a.vec, a.iov_cnt); else n = custom_writev(a.fd, a.vec, a.iov_cnt, a.batch_len); } while (writev_check(&a, n, "writev", io_wait) != 0); rb_str_resize(a.vec_buf, 0); if (TYPE(a.buf) != T_SYMBOL) kgio_autopush_write(io); return a.buf; } /* * call-seq: * * io.kgio_writev(array) -> nil * * Returns nil when the write completes. * * This may block and call any method defined to +kgio_wait_writable+ * for the class. * * Note: it uses +Array()+ semantic for converting argument, so that * it will succeed if you pass something else. */ static VALUE kgio_writev(VALUE io, VALUE ary) { return my_writev(io, ary, 1); } /* * call-seq: * * io.kgio_trywritev(array) -> nil, Array or :wait_writable * * Returns nil if the write was completed in full. * * Returns an Array of strings containing the unwritten portion * if EAGAIN was encountered, but some portion was successfully written. * * Returns :wait_writable if EAGAIN is encountered and nothing * was written. * * Note: it uses +Array()+ semantic for converting argument, so that * it will succeed if you pass something else. */ static VALUE kgio_trywritev(VALUE io, VALUE ary) { return my_writev(io, ary, 0); } /* * call-seq: * * Kgio.trywritev(io, array) -> nil, Array or :wait_writable * * Returns nil if the write was completed in full. * * Returns a Array of strings containing the unwritten portion if EAGAIN * was encountered, but some portion was successfully written. * * Returns :wait_writable if EAGAIN is encountered and nothing * was written. * * Maybe used in place of PipeMethods#kgio_trywritev for non-Kgio objects */ static VALUE s_trywritev(VALUE mod, VALUE io, VALUE ary) { return kgio_trywritev(io, ary); } void init_kgio_writev(void) { #ifdef IOV_MAX int sys_iov_max = IOV_MAX; #else int sys_iov_max = (int)sysconf(_SC_IOV_MAX); #endif VALUE mPipeMethods, mSocketMethods; VALUE mKgio = rb_define_module("Kgio"); if (sys_iov_max < iov_max) iov_max = sys_iov_max; sym_wait_writable = ID2SYM(rb_intern("wait_writable")); rb_define_singleton_method(mKgio, "trywritev", s_trywritev, 2); mPipeMethods = rb_define_module_under(mKgio, "PipeMethods"); rb_define_method(mPipeMethods, "kgio_writev", kgio_writev, 1); rb_define_method(mPipeMethods, "kgio_trywritev", kgio_trywritev, 1); mSocketMethods = rb_define_module_under(mKgio, "SocketMethods"); rb_define_method(mSocketMethods, "kgio_writev", kgio_writev, 1); rb_define_method(mSocketMethods, "kgio_trywritev", kgio_trywritev, 1); } kgio-2.11.2/ext/kgio/accept.c0000644000004100000410000003475313241032461015722 0ustar www-datawww-data/* ref: rubinius b2811f260de16d1e972462e27852470364608de5 */ #define RSTRING_MODIFIED 1 #include "kgio.h" #include "missing_accept4.h" #include "sock_for_fd.h" #include "my_fileno.h" #include "nonblock.h" static VALUE localhost; static VALUE cClientSocket; static VALUE cKgio_Socket; static VALUE mSocketMethods; static VALUE iv_kgio_addr; #if defined(__linux__) && defined(KGIO_WITHOUT_GVL) static int accept4_flags = SOCK_CLOEXEC; #else /* ! linux */ static int accept4_flags = SOCK_CLOEXEC | SOCK_NONBLOCK; #endif /* ! linux */ struct accept_args { int fd; int flags; struct sockaddr *addr; socklen_t *addrlen; VALUE accept_io; VALUE accepted_class; }; /* * Sets the default class for newly accepted sockets. This is * legacy behavior, kgio_accept and kgio_tryaccept now take optional * class arguments to override this value. */ static VALUE set_accepted(VALUE klass, VALUE aclass) { VALUE tmp; if (NIL_P(aclass)) aclass = cKgio_Socket; tmp = rb_funcall(aclass, rb_intern("included_modules"), 0); tmp = rb_funcall(tmp, rb_intern("include?"), 1, mSocketMethods); if (tmp != Qtrue) rb_raise(rb_eTypeError, "class must include Kgio::SocketMethods"); cClientSocket = aclass; return aclass; } /* * Returns the default class for newly accepted sockets when kgio_accept * or kgio_tryaccept are not passed arguments */ static VALUE get_accepted(VALUE klass) { return cClientSocket; } /* * accept() wrapper that'll fall back on accept() if we were built on * a system with accept4() but run on a system without accept4() */ static VALUE xaccept(void *ptr) { struct accept_args *a = ptr; int rv; rv = accept_fn(a->fd, a->addr, a->addrlen, a->flags); if (rv < 0 && errno == ENOSYS && accept_fn != my_accept4) { accept_fn = my_accept4; rv = accept_fn(a->fd, a->addr, a->addrlen, a->flags); } return (VALUE)rv; } #ifdef KGIO_WITHOUT_GVL # include # include "blocking_io_region.h" static int thread_accept(struct accept_args *a, int force_nonblock) { if (force_nonblock) set_nonblocking(a->fd); return (int)rb_thread_io_blocking_region(xaccept, a, a->fd); } #else /* ! KGIO_WITHOUT_GVL */ # include static int thread_accept(struct accept_args *a, int force_nonblock) { int rv; /* always use non-blocking accept() under 1.8 for green threads */ set_nonblocking(a->fd); /* created sockets are always non-blocking under 1.8, too */ a->flags |= SOCK_NONBLOCK; TRAP_BEG; rv = (int)xaccept(a); TRAP_END; return rv; } #endif /* ! KGIO_WITHOUT_GVL */ static void prepare_accept(struct accept_args *a, VALUE self, int argc, const VALUE *argv) { a->fd = my_fileno(self); a->accept_io = self; switch (argc) { case 2: a->flags = NUM2INT(argv[1]); a->accepted_class = NIL_P(argv[0]) ? cClientSocket : argv[0]; return; case 0: /* default, legacy behavior */ a->flags = accept4_flags; a->accepted_class = cClientSocket; return; case 1: a->flags = accept4_flags; a->accepted_class = NIL_P(argv[0]) ? cClientSocket : argv[0]; return; } rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc); } static VALUE in_addr_set(VALUE io, struct sockaddr_storage *addr, socklen_t len) { VALUE host; int host_len, rc; char *host_ptr; switch (addr->ss_family) { case AF_INET: host_len = (long)INET_ADDRSTRLEN; break; case AF_INET6: host_len = (long)INET6_ADDRSTRLEN; break; default: rb_raise(rb_eRuntimeError, "unsupported address family: ss_family=%lu (socklen=%ld)", (unsigned long)addr->ss_family, (long)len); } host = rb_str_new(NULL, host_len); host_ptr = RSTRING_PTR(host); rc = getnameinfo((struct sockaddr *)addr, len, host_ptr, host_len, NULL, 0, NI_NUMERICHOST); if (rc != 0) rb_raise(rb_eRuntimeError, "getnameinfo: %s", gai_strerror(rc)); rb_str_set_len(host, strlen(host_ptr)); return rb_ivar_set(io, iv_kgio_addr, host); } #if defined(__linux__) # define post_accept kgio_autopush_accept #else # define post_accept(a,b) for(;0;) #endif static VALUE my_accept(struct accept_args *a, int force_nonblock) { int client_fd; VALUE client_io; int retried = 0; retry: client_fd = thread_accept(a, force_nonblock); if (client_fd < 0) { switch (errno) { case EAGAIN: if (force_nonblock) return Qnil; a->fd = my_fileno(a->accept_io); errno = EAGAIN; (void)rb_io_wait_readable(a->fd); /* fall-through to EINTR case */ #ifdef ECONNABORTED case ECONNABORTED: #endif /* ECONNABORTED */ #ifdef EPROTO case EPROTO: #endif /* EPROTO */ case EINTR: /* raise IOError if closed during sleep */ a->fd = my_fileno(a->accept_io); goto retry; case ENOMEM: case EMFILE: case ENFILE: #ifdef ENOBUFS case ENOBUFS: #endif /* ENOBUFS */ if (!retried) { retried = 1; errno = 0; rb_gc(); goto retry; } default: rb_sys_fail("accept"); } } client_io = sock_for_fd(a->accepted_class, client_fd); post_accept(a->accept_io, client_io); if (a->addr) in_addr_set(client_io, (struct sockaddr_storage *)a->addr, *a->addrlen); else rb_ivar_set(client_io, iv_kgio_addr, localhost); return client_io; } /* * call-seq: * * io.kgio_addr! => refreshes the given sock address */ static VALUE addr_bang(VALUE io) { int fd = my_fileno(io); struct sockaddr_storage addr; socklen_t len = sizeof(struct sockaddr_storage); if (getpeername(fd, (struct sockaddr *)&addr, &len) != 0) rb_sys_fail("getpeername"); if (addr.ss_family == AF_UNIX) return rb_ivar_set(io, iv_kgio_addr, localhost); return in_addr_set(io, &addr, len); } /* * call-seq: * * server = Kgio::TCPServer.new('0.0.0.0', 80) * server.kgio_tryaccept -> Kgio::Socket or nil * server.kgio_tryaccept(klass = MySocket) -> MySocket or nil * server.kgio_tryaccept(nil, flags) -> Kgio::Socket or nil * * Initiates a non-blocking accept and returns a generic Kgio::Socket * object with the kgio_addr attribute set to the IP address of the * connected client on success. * * Returns nil on EAGAIN, and raises on other errors. * * An optional +klass+ argument may be specified to override the * Kgio::Socket-class on a successful return value. * * An optional +flags+ argument may also be specified. * +flags+ is a bitmask that may contain any combination of: * * - Kgio::SOCK_CLOEXEC - close-on-exec flag (enabled by default) * - Kgio::SOCK_NONBLOCK - non-blocking flag (unimportant) */ static VALUE tcp_tryaccept(int argc, VALUE *argv, VALUE self) { struct sockaddr_storage addr; socklen_t addrlen = sizeof(struct sockaddr_storage); struct accept_args a; a.addr = (struct sockaddr *)&addr; a.addrlen = &addrlen; prepare_accept(&a, self, argc, argv); return my_accept(&a, 1); } /* * call-seq: * * server = Kgio::TCPServer.new('0.0.0.0', 80) * server.kgio_accept -> Kgio::Socket or nil * server.kgio_tryaccept -> Kgio::Socket or nil * server.kgio_tryaccept(klass = MySocket) -> MySocket or nil * * Initiates a blocking accept and returns a generic Kgio::Socket * object with the kgio_addr attribute set to the IP address of * the client on success. * * On Ruby implementations using native threads, this can use a blocking * accept(2) (or accept4(2)) system call to avoid thundering herds. * * An optional +klass+ argument may be specified to override the * Kgio::Socket-class on a successful return value. * * An optional +flags+ argument may also be specified. * +flags+ is a bitmask that may contain any combination of: * * - Kgio::SOCK_CLOEXEC - close-on-exec flag (enabled by default) * - Kgio::SOCK_NONBLOCK - non-blocking flag (unimportant) */ static VALUE tcp_accept(int argc, VALUE *argv, VALUE self) { struct sockaddr_storage addr; socklen_t addrlen = sizeof(struct sockaddr_storage); struct accept_args a; a.addr = (struct sockaddr *)&addr; a.addrlen = &addrlen; prepare_accept(&a, self, argc, argv); return my_accept(&a, 0); } /* * call-seq: * * server = Kgio::UNIXServer.new("/path/to/unix/socket") * server.kgio_tryaccept -> Kgio::Socket or nil * server.kgio_tryaccept(klass = MySocket) -> MySocket or nil * server.kgio_tryaccept(nil, flags) -> Kgio::Socket or nil * * Initiates a non-blocking accept and returns a generic Kgio::Socket * object with the kgio_addr attribute set (to the value of * Kgio::LOCALHOST) on success. * * An optional +klass+ argument may be specified to override the * Kgio::Socket-class on a successful return value. * * An optional +flags+ argument may also be specified. * +flags+ is a bitmask that may contain any combination of: * * - Kgio::SOCK_CLOEXEC - close-on-exec flag (enabled by default) * - Kgio::SOCK_NONBLOCK - non-blocking flag (unimportant) */ static VALUE unix_tryaccept(int argc, VALUE *argv, VALUE self) { struct accept_args a; a.addr = NULL; a.addrlen = NULL; prepare_accept(&a, self, argc, argv); return my_accept(&a, 1); } /* * call-seq: * * server = Kgio::UNIXServer.new("/path/to/unix/socket") * server.kgio_accept -> Kgio::Socket or nil * server.kgio_accept(klass = MySocket) -> MySocket or nil * server.kgio_accept(nil, flags) -> Kgio::Socket or nil * * Initiates a blocking accept and returns a generic Kgio::Socket * object with the kgio_addr attribute set (to the value of * Kgio::LOCALHOST) on success. * * On Ruby implementations using native threads, this can use a blocking * accept(2) (or accept4(2)) system call to avoid thundering herds. * * An optional +klass+ argument may be specified to override the * Kgio::Socket-class on a successful return value. * * An optional +flags+ argument may also be specified. * +flags+ is a bitmask that may contain any combination of: * * - Kgio::SOCK_CLOEXEC - close-on-exec flag (enabled by default) * - Kgio::SOCK_NONBLOCK - non-blocking flag (unimportant) */ static VALUE unix_accept(int argc, VALUE *argv, VALUE self) { struct accept_args a; a.addr = NULL; a.addrlen = NULL; prepare_accept(&a, self, argc, argv); return my_accept(&a, 0); } /* * call-seq: * * Kgio.accept_cloexec? -> true or false * * Returns true if newly accepted Kgio::Sockets are created with the * FD_CLOEXEC file descriptor flag, false if not. * * Deprecated, use the per-socket flags for kgio_*accept instead. */ static VALUE get_cloexec(VALUE mod) { return (accept4_flags & SOCK_CLOEXEC) == SOCK_CLOEXEC ? Qtrue : Qfalse; } /* * * call-seq: * * Kgio.accept_nonblock? -> true or false * * Returns true if newly accepted Kgio::Sockets are created with the * O_NONBLOCK file status flag, false if not. * * Deprecated, use the per-socket flags for kgio_*accept instead. */ static VALUE get_nonblock(VALUE mod) { return (accept4_flags & SOCK_NONBLOCK)==SOCK_NONBLOCK ? Qtrue : Qfalse; } /* * call-seq: * * Kgio.accept_cloexec = true * Kgio.accept_cloexec = false * * Sets whether or not Kgio::Socket objects created by * TCPServer#kgio_accept, * TCPServer#kgio_tryaccept, * UNIXServer#kgio_accept, * and UNIXServer#kgio_tryaccept * default to being created with the FD_CLOEXEC file descriptor flag. * * This is on by default, as there is little reason to deal to enable * it for client sockets on a socket server. * * Deprecated, use the per-socket flags for kgio_*accept instead. */ static VALUE set_cloexec(VALUE mod, VALUE boolean) { switch (TYPE(boolean)) { case T_TRUE: accept4_flags |= SOCK_CLOEXEC; return boolean; case T_FALSE: accept4_flags &= ~SOCK_CLOEXEC; return boolean; } rb_raise(rb_eTypeError, "not true or false"); return Qnil; } /* * call-seq: * * Kgio.accept_nonblock = true * Kgio.accept_nonblock = false * * Sets whether or not Kgio::Socket objects created by * TCPServer#kgio_accept, * TCPServer#kgio_tryaccept, * UNIXServer#kgio_accept, * and UNIXServer#kgio_tryaccept * are created with the O_NONBLOCK file status flag. * * This defaults to +false+ for GNU/Linux where MSG_DONTWAIT is * available (and on newer GNU/Linux, accept4() may also set * the non-blocking flag. This defaults to +true+ on non-GNU/Linux * systems. * * This is always true on Ruby implementations using user-space threads. * * Deprecated, use the per-socket flags for kgio_*accept instead. */ static VALUE set_nonblock(VALUE mod, VALUE boolean) { switch (TYPE(boolean)) { case T_TRUE: accept4_flags |= SOCK_NONBLOCK; return boolean; case T_FALSE: accept4_flags &= ~SOCK_NONBLOCK; return boolean; } rb_raise(rb_eTypeError, "not true or false"); return Qnil; } void init_kgio_accept(void) { VALUE cUNIXServer, cTCPServer; VALUE mKgio = rb_define_module("Kgio"); /* * Maps to the SOCK_NONBLOCK constant in Linux for setting * the non-blocking flag on newly accepted sockets. This is * usually unnecessary as sockets are made non-blocking * whenever non-blocking methods are used. */ rb_define_const(mKgio, "SOCK_NONBLOCK", INT2NUM(SOCK_NONBLOCK)); /* * Maps to the SOCK_CLOEXEC constant in Linux for setting * the close-on-exec flag on newly accepted descriptors. This * is enabled by default, and there is usually no reason to * disable close-on-exec for accepted sockets. */ rb_define_const(mKgio, "SOCK_CLOEXEC", INT2NUM(SOCK_CLOEXEC)); localhost = rb_const_get(mKgio, rb_intern("LOCALHOST")); cKgio_Socket = rb_const_get(mKgio, rb_intern("Socket")); cClientSocket = cKgio_Socket; mSocketMethods = rb_const_get(mKgio, rb_intern("SocketMethods")); rb_define_method(mSocketMethods, "kgio_addr!", addr_bang, 0); rb_define_singleton_method(mKgio, "accept_cloexec?", get_cloexec, 0); rb_define_singleton_method(mKgio, "accept_cloexec=", set_cloexec, 1); rb_define_singleton_method(mKgio, "accept_nonblock?", get_nonblock, 0); rb_define_singleton_method(mKgio, "accept_nonblock=", set_nonblock, 1); rb_define_singleton_method(mKgio, "accept_class=", set_accepted, 1); rb_define_singleton_method(mKgio, "accept_class", get_accepted, 0); /* * Document-class: Kgio::UNIXServer * * Kgio::UNIXServer should be used in place of the plain UNIXServer * when kgio_accept and kgio_tryaccept methods are needed. */ cUNIXServer = rb_const_get(rb_cObject, rb_intern("UNIXServer")); cUNIXServer = rb_define_class_under(mKgio, "UNIXServer", cUNIXServer); rb_define_method(cUNIXServer, "kgio_tryaccept", unix_tryaccept, -1); rb_define_method(cUNIXServer, "kgio_accept", unix_accept, -1); /* * Document-class: Kgio::TCPServer * * Kgio::TCPServer should be used in place of the plain TCPServer * when kgio_accept and kgio_tryaccept methods are needed. */ cTCPServer = rb_const_get(rb_cObject, rb_intern("TCPServer")); cTCPServer = rb_define_class_under(mKgio, "TCPServer", cTCPServer); rb_define_method(cTCPServer, "kgio_tryaccept", tcp_tryaccept, -1); rb_define_method(cTCPServer, "kgio_accept", tcp_accept, -1); init_sock_for_fd(); iv_kgio_addr = rb_intern("@kgio_addr"); } kgio-2.11.2/ext/kgio/extconf.rb0000644000004100000410000000400413241032461016274 0ustar www-datawww-datarequire 'mkmf' $CPPFLAGS << ' -D_GNU_SOURCE' $CPPFLAGS << ' -DPOSIX_C_SOURCE=1' $CPPFLAGS += '-D_POSIX_C_SOURCE=200112L' unless have_macro('CLOCK_MONOTONIC', 'time.h') have_func('CLOCK_MONOTONIC', 'time.h') end have_type('clockid_t', 'time.h') have_library('rt', 'clock_gettime', 'time.h') # taken from ext/socket/extconf.rb in ruby/trunk: # OpenSolaris: have_library("nsl", "t_open") have_library("socket", "socket") have_func("poll", "poll.h") have_func("getaddrinfo", %w(sys/types.h sys/socket.h netdb.h)) or abort "getaddrinfo required" have_func("getnameinfo", %w(sys/types.h sys/socket.h netdb.h)) or abort "getnameinfo required" have_type("struct sockaddr_storage", %w(sys/types.h sys/socket.h)) or abort "struct sockaddr_storage required" have_func('accept4', %w(sys/socket.h)) have_header("sys/select.h") have_func("writev", "sys/uio.h") if have_header('ruby/io.h') rubyio = %w(ruby.h ruby/io.h) have_struct_member("rb_io_t", "fd", rubyio) have_struct_member("rb_io_t", "mode", rubyio) have_struct_member("rb_io_t", "pathv", rubyio) else rubyio = %w(ruby.h rubyio.h) rb_io_t = have_type("OpenFile", rubyio) ? "OpenFile" : "rb_io_t" have_struct_member(rb_io_t, "f", rubyio) have_struct_member(rb_io_t, "f2", rubyio) have_struct_member(rb_io_t, "mode", rubyio) have_struct_member(rb_io_t, "path", rubyio) have_func('rb_fdopen') end have_type("struct RFile", rubyio) and check_sizeof("struct RFile", rubyio) have_type("struct RObject") and check_sizeof("struct RObject") check_sizeof("int") have_func('rb_io_ascii8bit_binmode') have_func('rb_update_max_fd') have_func('rb_fd_fix_cloexec') have_func('rb_cloexec_open') have_header('ruby/thread.h') have_func('rb_thread_call_without_gvl', %w{ruby/thread.h}) have_func('rb_thread_blocking_region') have_func('rb_thread_io_blocking_region') have_func('rb_str_set_len') have_func("rb_hash_clear", "ruby.h") # Ruby 2.0+ have_func('rb_time_interval') have_func('rb_wait_for_single_fd') have_func('rb_str_subseq') have_func('rb_ary_subseq') create_makefile('kgio_ext') kgio-2.11.2/ext/kgio/blocking_io_region.h0000644000004100000410000000103313241032461020273 0ustar www-datawww-data#ifdef KGIO_WITHOUT_GVL # if defined(HAVE_RB_THREAD_IO_BLOCKING_REGION) /* temporary API for Ruby 1.9.3 */ VALUE rb_thread_io_blocking_region(rb_blocking_function_t *, void *, int); # elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) # define rb_thread_io_blocking_region(fn,data,fd) \ rb_thread_call_without_gvl((fn),(data),RUBY_UBF_IO,0) # elif defined(HAVE_RB_THREAD_BLOCKING_REGION) # define rb_thread_io_blocking_region(fn,data,fd) \ rb_thread_blocking_region((fn),(data),RUBY_UBF_IO,0) # endif #endif kgio-2.11.2/ext/kgio/write.c0000644000004100000410000001444613241032461015612 0ustar www-datawww-data/* we do not modify RSTRING pointers here */ #include "kgio.h" #include "my_fileno.h" #include "nonblock.h" static VALUE sym_wait_writable; struct wr_args { VALUE io; VALUE buf; const char *ptr; long len; int fd; int flags; }; static void prepare_write(struct wr_args *a, VALUE io, VALUE str) { a->buf = (TYPE(str) == T_STRING) ? str : rb_obj_as_string(str); a->ptr = RSTRING_PTR(a->buf); a->len = RSTRING_LEN(a->buf); a->io = io; a->fd = my_fileno(io); } static int write_check(struct wr_args *a, long n, const char *msg, int io_wait) { if (a->len == n) { done: a->buf = Qnil; } else if (n < 0) { if (errno == EINTR) { a->fd = my_fileno(a->io); return -1; } if (errno == EAGAIN) { long written = RSTRING_LEN(a->buf) - a->len; if (io_wait) { (void)kgio_call_wait_writable(a->io); /* buf may be modified in other thread/fiber */ a->len = RSTRING_LEN(a->buf) - written; if (a->len <= 0) goto done; a->ptr = RSTRING_PTR(a->buf) + written; return -1; } else if (written > 0) { a->buf = MY_STR_SUBSEQ(a->buf, written, a->len); } else { a->buf = sym_wait_writable; } return 0; } kgio_wr_sys_fail(msg); } else { assert(n >= 0 && n < a->len && "write/send syscall broken?"); a->ptr += n; a->len -= n; return -1; } return 0; } static VALUE my_write(VALUE io, VALUE str, int io_wait) { struct wr_args a; long n; prepare_write(&a, io, str); set_nonblocking(a.fd); retry: n = (long)write(a.fd, a.ptr, a.len); if (write_check(&a, n, "write", io_wait) != 0) goto retry; if (TYPE(a.buf) != T_SYMBOL) kgio_autopush_write(io); return a.buf; } /* * call-seq: * * io.kgio_write(str) -> nil * * Returns nil when the write completes. * * This may block and call any method defined to +kgio_wait_writable+ * for the class. */ static VALUE kgio_write(VALUE io, VALUE str) { return my_write(io, str, 1); } /* * call-seq: * * io.kgio_trywrite(str) -> nil, String or :wait_writable * * Returns nil if the write was completed in full. * * Returns a String containing the unwritten portion if EAGAIN * was encountered, but some portion was successfully written. * * Returns :wait_writable if EAGAIN is encountered and nothing * was written. */ static VALUE kgio_trywrite(VALUE io, VALUE str) { return my_write(io, str, 0); } #ifdef USE_MSG_DONTWAIT /* * This method behaves like Kgio::PipeMethods#kgio_write, except * it will use send(2) with the MSG_DONTWAIT flag on sockets to * avoid unnecessary calls to fcntl(2). */ static VALUE my_send(VALUE io, VALUE str, int io_wait) { struct wr_args a; long n; prepare_write(&a, io, str); retry: n = (long)send(a.fd, a.ptr, a.len, MSG_DONTWAIT); if (write_check(&a, n, "send", io_wait) != 0) goto retry; if (TYPE(a.buf) != T_SYMBOL) kgio_autopush_send(io); return a.buf; } /* * This method may be optimized on some systems (e.g. GNU/Linux) to use * MSG_DONTWAIT to avoid explicitly setting the O_NONBLOCK flag via fcntl. * Otherwise this is the same as Kgio::PipeMethods#kgio_write */ static VALUE kgio_send(VALUE io, VALUE str) { return my_send(io, str, 1); } /* * This method may be optimized on some systems (e.g. GNU/Linux) to use * MSG_DONTWAIT to avoid explicitly setting the O_NONBLOCK flag via fcntl. * Otherwise this is the same as Kgio::PipeMethods#kgio_trywrite */ static VALUE kgio_trysend(VALUE io, VALUE str) { return my_send(io, str, 0); } #else /* ! USE_MSG_DONTWAIT */ # define kgio_send kgio_write # define kgio_trysend kgio_trywrite #endif /* ! USE_MSG_DONTWAIT */ #if defined(KGIO_WITHOUT_GVL) # include "blocking_io_region.h" #ifdef MSG_DONTWAIT /* Linux only */ # define MY_MSG_DONTWAIT (MSG_DONTWAIT) #else # define MY_MSG_DONTWAIT (0) #endif static VALUE nogvl_send(void *ptr) { struct wr_args *a = ptr; return (VALUE)send(a->fd, a->ptr, a->len, a->flags); } /* * call-seq: * * io.kgio_syssend(str, flags) -> nil, String or :wait_writable * * Returns nil if the write was completed in full. * * Returns a String containing the unwritten portion if EAGAIN * was encountered, but some portion was successfully written. * * Returns :wait_writable if EAGAIN is encountered and nothing * was written. * * This method is only available on Ruby 1.9.3 or later. */ static VALUE kgio_syssend(VALUE io, VALUE str, VALUE flags) { struct wr_args a; long n; a.flags = NUM2INT(flags); prepare_write(&a, io, str); if (a.flags & MY_MSG_DONTWAIT) { do { n = (long)send(a.fd, a.ptr, a.len, a.flags); } while (write_check(&a, n, "send", 0) != 0); } else { do { n = (long)rb_thread_io_blocking_region( nogvl_send, &a, a.fd); } while (write_check(&a, n, "send", 0) != 0); } return a.buf; } #endif /* HAVE_RB_THREAD_IO_BLOCKING_REGION */ /* * call-seq: * * Kgio.trywrite(io, str) -> nil, String or :wait_writable * * Returns nil if the write was completed in full. * * Returns a String containing the unwritten portion if EAGAIN * was encountered, but some portion was successfully written. * * Returns :wait_writable if EAGAIN is encountered and nothing * was written. * * Maybe used in place of PipeMethods#kgio_trywrite for non-Kgio objects */ static VALUE s_trywrite(VALUE mod, VALUE io, VALUE str) { return my_write(io, str, 0); } void init_kgio_write(void) { VALUE mPipeMethods, mSocketMethods; VALUE mKgio = rb_define_module("Kgio"); sym_wait_writable = ID2SYM(rb_intern("wait_writable")); rb_define_singleton_method(mKgio, "trywrite", s_trywrite, 2); /* * Document-module: Kgio::PipeMethods * * This module may be used used to create classes that respond to * various Kgio methods for reading and writing. This is included * in Kgio::Pipe by default. */ mPipeMethods = rb_define_module_under(mKgio, "PipeMethods"); rb_define_method(mPipeMethods, "kgio_write", kgio_write, 1); rb_define_method(mPipeMethods, "kgio_trywrite", kgio_trywrite, 1); /* * Document-module: Kgio::SocketMethods * * This method behaves like Kgio::PipeMethods, but contains * optimizations for sockets on certain operating systems * (e.g. GNU/Linux). */ mSocketMethods = rb_define_module_under(mKgio, "SocketMethods"); rb_define_method(mSocketMethods, "kgio_write", kgio_send, 1); rb_define_method(mSocketMethods, "kgio_trywrite", kgio_trysend, 1); #if defined(KGIO_WITHOUT_GVL) rb_define_method(mSocketMethods, "kgio_syssend", kgio_syssend, 2); #endif } kgio-2.11.2/ext/kgio/tryopen.c0000644000004100000410000001162113241032461016150 0ustar www-datawww-data/* We do not modify RSTRING in this file, so RSTRING_MODIFIED is not needed */ #include #ifdef HAVE_RUBY_IO_H # include #else # include #endif #ifdef HAVE_RUBY_ST_H # include #else # include #endif #include #include #include #include #include "set_file_path.h" #include "ancient_ruby.h" #include "kgio.h" static ID id_for_fd, id_to_path, id_path; static st_table *errno2sym; struct open_args { const char *pathname; int flags; mode_t mode; }; #ifndef HAVE_RB_CLOEXEC_OPEN # define rb_cloexec_open(p,f,m) open((p),(f),(m)) #endif static void * nogvl_open(void *ptr) { struct open_args *o = ptr; long fd = (long)rb_cloexec_open(o->pathname, o->flags, o->mode); return (void *)fd; } #ifndef KGIO_WITHOUT_GVL # define RUBY_UBF_IO ((void *)(-1)) # include "rubysig.h" typedef void my_unblock_function_t(void *); typedef void *my_blocking_function_t(void *); static void * my_thread_blocking_region( my_blocking_function_t *fn, void *data1, my_unblock_function_t *ubf, void *data2) { void *rv; TRAP_BEG; /* for FIFO */ rv = fn(data1); TRAP_END; return rv; } #define KGIO_WITHOUT_GVL(fn,data1,ubf,data2) \ my_thread_blocking_region((fn),(data1),(ubf),(data2)) #endif /* ! KGIO_WITHOUT_GVL */ /* * call-seq: * * Kgio::File.tryopen(filename, [, mode [, perm]]) -> Kgio::File or Symbol * * Returns a Kgio::File object on a successful open. +filename+ is a * path to any file on the filesystem. If specified, +mode+ is a bitmask * of flags (see IO.sysopen) and +perm+ should be an octal number. * * This does not raise errors for most failures, but installs returns a * Ruby symbol for the constant in the Errno::* namespace. * * Common error symbols are: * * - :ENOENT * - :EACCES * * See your open(2) manpage for more information on open(2) errors. */ static VALUE s_tryopen(int argc, VALUE *argv, VALUE klass) { long fd; VALUE pathname, flags, mode; struct open_args o; int retried = 0; VALUE rv; rb_scan_args(argc, argv, "12", &pathname, &flags, &mode); if (rb_respond_to(pathname, id_to_path)) pathname = rb_funcall(pathname, id_to_path, 0); o.pathname = StringValueCStr(pathname); switch (TYPE(flags)) { case T_NIL: o.flags = O_RDONLY; break; case T_FIXNUM: o.flags = FIX2INT(flags); break; case T_BIGNUM: o.flags = NUM2INT(flags); break; default: rb_raise(rb_eArgError, "flags must be an Integer"); } switch (TYPE(mode)) { case T_NIL: o.mode = 0666; break; case T_FIXNUM: o.mode = FIX2INT(mode); break; case T_BIGNUM: o.mode = NUM2INT(mode); break; default: rb_raise(rb_eArgError, "mode must be an Integer"); } retry: fd = (long)KGIO_WITHOUT_GVL(nogvl_open, &o, RUBY_UBF_IO, 0); if (fd < 0) { if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) { rb_gc(); if (retried) rb_sys_fail(o.pathname); retried = 1; goto retry; } if (fd < 0) { int saved_errno = errno; if (!st_lookup(errno2sym, (st_data_t)errno, &rv)) { errno = saved_errno; rb_sys_fail(o.pathname); } return rv; } } rv = rb_funcall(klass, id_for_fd, 1, LONG2FIX(fd)); set_file_path(rv, pathname); return rv; } void init_kgio_tryopen(void) { VALUE mKgio = rb_define_module("Kgio"); VALUE mPipeMethods = rb_const_get(mKgio, rb_intern("PipeMethods")); VALUE cFile; VALUE tmp; long i, len; id_path = rb_intern("path"); id_for_fd = rb_intern("for_fd"); id_to_path = rb_intern("to_path"); /* * Document-class: Kgio::File * * This subclass of the core File class adds the "tryopen" singleton * method for opening files. A single "tryopen" and check for the * return value may be used to avoid unnecessary stat(2) syscalls * or File.open exceptions when checking for the existence of a file * and opening it. */ cFile = rb_define_class_under(mKgio, "File", rb_cFile); rb_define_singleton_method(cFile, "tryopen", s_tryopen, -1); rb_include_module(cFile, mPipeMethods); if (!rb_funcall(cFile, rb_intern("method_defined?"), 1, ID2SYM(id_to_path))) rb_define_alias(cFile, "to_path", "path"); errno2sym = st_init_numtable(); tmp = rb_funcall(rb_mErrno, rb_intern("constants"), 0); len = RARRAY_LEN(tmp); for (i = 0; i < len; i++) { VALUE error; VALUE err = rb_ary_entry(tmp, i); ID const_id; switch (TYPE(err)) { case T_SYMBOL: const_id = SYM2ID(err); break; case T_STRING: const_id = rb_intern(RSTRING_PTR(err)); break; default: { VALUE i = rb_inspect(err); const char *s = RSTRING_PTR(i); rb_bug("constant not a symbol or string: %s", s); RB_GC_GUARD(i); } } error = rb_const_get(rb_mErrno, const_id); if ((TYPE(error) != T_CLASS) || !rb_const_defined(error, rb_intern("Errno"))) continue; error = rb_const_get(error, rb_intern("Errno")); switch (TYPE(error)) { case T_FIXNUM: case T_BIGNUM: st_insert(errno2sym, (st_data_t)NUM2INT(error), ID2SYM(const_id)); } } RB_GC_GUARD(tmp); } kgio-2.11.2/ext/kgio/wait.c0000644000004100000410000000713313241032461015417 0ustar www-datawww-data#include "kgio.h" #include "my_fileno.h" static ID id_wait_rd, id_wait_wr; #if defined(HAVE_RB_TIME_INTERVAL) && defined(HAVE_RB_WAIT_FOR_SINGLE_FD) static int kgio_timedwait(VALUE self, VALUE timeout, int write_p) { struct timeval tv = rb_time_interval(timeout); int events = write_p ? RB_WAITFD_OUT : RB_WAITFD_IN; return rb_wait_for_single_fd(my_fileno(self), events, &tv); } #else /* ! (HAVE_RB_TIME_INTERVAL && HAVE_RB_WAIT_FOR_SINGLE_FD) */ static int kgio_timedwait(VALUE self, VALUE timeout, int write_p) { VALUE argv[4]; VALUE set = rb_ary_new3(1, self); argv[0] = write_p ? Qnil : set; argv[1] = write_p ? set : Qnil; argv[2] = Qnil; argv[3] = timeout; set = rb_funcall2(rb_cIO, rb_intern("select"), 4, argv); return NIL_P(set) ? 0 : 1; } #endif /* ! (HAVE_RB_TIME_INTERVAL && HAVE_RB_WAIT_FOR_SINGLE_FD) */ static int kgio_wait(int argc, VALUE *argv, VALUE self, int write_p) { int fd; VALUE timeout; if (rb_scan_args(argc, argv, "01", &timeout) == 1 && !NIL_P(timeout)) return kgio_timedwait(self, timeout, write_p); fd = my_fileno(self); errno = EAGAIN; write_p ? rb_io_wait_writable(fd) : rb_io_wait_readable(fd); return 1; } /* * call-seq: * * io.kgio_wait_readable -> IO * io.kgio_wait_readable(timeout) -> IO or nil * * Blocks the running Thread indefinitely until the IO object is readable * or if +timeout+ expires. If +timeout+ is specified and expires, +nil+ * is returned. * * This method is automatically called (without timeout argument) by default * whenever kgio_read needs to block on input. * * Users of alternative threading/fiber libraries are * encouraged to override this method in their subclasses or modules to * work with their threading/blocking methods. */ static VALUE kgio_wait_readable(int argc, VALUE *argv, VALUE self) { int r = kgio_wait(argc, argv, self, 0); if (r < 0) rb_sys_fail("kgio_wait_readable"); return r == 0 ? Qnil : self; } /* * call-seq: * * io.kgio_wait_writable -> IO * io.kgio_wait_writable(timeout) -> IO or nil * * Blocks the running Thread indefinitely until the IO object is writable * or if +timeout+ expires. If +timeout+ is specified and expires, +nil+ * is returned. * * This method is automatically called (without timeout argument) by default * whenever kgio_write needs to block on output. * * Users of alternative threading/fiber libraries are * encouraged to override this method in their subclasses or modules to * work with their threading/blocking methods. */ static VALUE kgio_wait_writable(int argc, VALUE *argv, VALUE self) { int r = kgio_wait(argc, argv, self, 1); if (r < 0) rb_sys_fail("kgio_wait_writable"); return r == 0 ? Qnil : self; } VALUE kgio_call_wait_writable(VALUE io) { return rb_funcall(io, id_wait_wr, 0); } VALUE kgio_call_wait_readable(VALUE io) { return rb_funcall(io, id_wait_rd, 0); } void init_kgio_wait(void) { VALUE mKgio = rb_define_module("Kgio"); /* * Document-module: Kgio::DefaultWaiters * * This module contains default kgio_wait_readable and * kgio_wait_writable methods that block indefinitely (in a * thread-safe manner) until an IO object is read or writable. * This module is included in the Kgio::PipeMethods and * Kgio::SocketMethods modules used by all bundled IO-derived * objects. */ VALUE mWaiters = rb_define_module_under(mKgio, "DefaultWaiters"); id_wait_rd = rb_intern("kgio_wait_readable"); id_wait_wr = rb_intern("kgio_wait_writable"); rb_define_method(mWaiters, "kgio_wait_readable", kgio_wait_readable, -1); rb_define_method(mWaiters, "kgio_wait_writable", kgio_wait_writable, -1); } kgio-2.11.2/ext/kgio/missing_accept4.h0000644000004100000410000000302713241032461017532 0ustar www-datawww-data#if !defined(HAVE_ACCEPT4) || !defined(SOCK_CLOEXEC) || !defined(SOCK_NONBLOCK) # ifndef _GNU_SOURCE # define _GNU_SOURCE # endif # include # include # ifndef SOCK_CLOEXEC # if (02000000 == O_NONBLOCK) # define SOCK_CLOEXEC 1 # define SOCK_NONBLOCK 2 # else # define SOCK_CLOEXEC 02000000 # define SOCK_NONBLOCK O_NONBLOCK # endif # endif #endif /* !HAVE_ACCEPT4 */ /* accept4() is currently a Linux-only goodie */ static int my_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) { int fd = accept(sockfd, addr, addrlen); if (fd >= 0) { if ((flags & SOCK_CLOEXEC) == SOCK_CLOEXEC) (void)fcntl(fd, F_SETFD, FD_CLOEXEC); /* * Some systems inherit O_NONBLOCK across accept(). * We also expect our users to use MSG_DONTWAIT under * Linux, so fcntl() is completely unnecessary * in most cases... */ if ((flags & SOCK_NONBLOCK) == SOCK_NONBLOCK) { int fl = fcntl(fd, F_GETFL); /* * unconditional, OSX 10.4 (and maybe other *BSDs) * F_GETFL returns a false O_NONBLOCK with TCP sockets * (but not UNIX sockets) [ruby-talk:274079] */ (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK); } /* * nothing we can do about fcntl() errors in this wrapper * function, let the user (Ruby) code figure it out */ errno = 0; } return fd; } typedef int accept_fn_t(int, struct sockaddr *, socklen_t *, int); #ifdef HAVE_ACCEPT4 static accept_fn_t *accept_fn = accept4; #else static accept_fn_t *accept_fn = my_accept4; #endif kgio-2.11.2/ext/kgio/set_file_path.h0000644000004100000410000000130713241032461017263 0ustar www-datawww-data/* We do not modify RSTRING in this file, so RSTRING_MODIFIED is not needed */ #if defined(HAVE_RB_IO_T) && \ defined(HAVE_TYPE_STRUCT_RFILE) && \ defined(HAVE_ST_PATHV) /* MRI 1.9 */ static void set_file_path(VALUE io, VALUE path) { rb_io_t *fptr = RFILE(io)->fptr; fptr->pathv = rb_str_new4(path); } #elif defined(HAVE_TYPE_OPENFILE) && \ defined(HAVE_TYPE_STRUCT_RFILE) && \ defined(HAVE_ST_PATH) /* MRI 1.8 */ #include "util.h" static void set_file_path(VALUE io, VALUE path) { OpenFile *fptr = RFILE(io)->fptr; fptr->path = ruby_strdup(RSTRING_PTR(path)); } #else /* Rubinius */ static void set_file_path(VALUE io, VALUE path) { rb_iv_set(io, "@path", rb_str_new4(path)); } #endif kgio-2.11.2/ext/kgio/autopush.c0000644000004100000410000001457513241032461016333 0ustar www-datawww-data/* * We use a very basic strategy to use TCP_CORK semantics optimally * in most TCP servers: On corked sockets, we will uncork on recv() * if there was a previous send(). Otherwise we do not fiddle * with TCP_CORK at all. * * Under Linux, we can rely on TCP_CORK being inherited in an * accept()-ed client socket so we can avoid syscalls for each * accept()-ed client if we know the accept() socket corks. * * This module does NOTHING for client TCP sockets, we only deal * with accept()-ed sockets right now. */ #include "kgio.h" #include "my_fileno.h" #include /* * As of FreeBSD 4.5, TCP_NOPUSH == TCP_CORK * ref: http://dotat.at/writing/nopush.html * We won't care for older FreeBSD since nobody runs Ruby on them... */ #ifdef TCP_CORK # define KGIO_NOPUSH TCP_CORK #elif defined(TCP_NOPUSH) # define KGIO_NOPUSH TCP_NOPUSH #endif #ifdef KGIO_NOPUSH static ID id_autopush_state; static int enabled = 1; enum autopush_state { AUTOPUSH_STATE_ACCEPTOR_IGNORE = -1, AUTOPUSH_STATE_IGNORE = 0, AUTOPUSH_STATE_WRITER = 1, AUTOPUSH_STATE_WRITTEN = 2, AUTOPUSH_STATE_ACCEPTOR = 3 }; #if defined(R_CAST) && \ defined(HAVE_TYPE_STRUCT_RFILE) && \ defined(HAVE_TYPE_STRUCT_ROBJECT) && \ ((SIZEOF_STRUCT_RFILE + SIZEOF_INT) <= (SIZEOF_STRUCT_ROBJECT)) struct AutopushSocket { struct RFile rfile; enum autopush_state autopush_state; }; static enum autopush_state state_get(VALUE io) { return ((struct AutopushSocket *)(io))->autopush_state; } static void state_set(VALUE io, enum autopush_state state) { ((struct AutopushSocket *)(io))->autopush_state = state; } #else static enum autopush_state state_get(VALUE io) { VALUE val; if (rb_ivar_defined(io, id_autopush_state) == Qfalse) return AUTOPUSH_STATE_IGNORE; val = rb_ivar_get(io, id_autopush_state); return (enum autopush_state)NUM2INT(val); } static void state_set(VALUE io, enum autopush_state state) { rb_ivar_set(io, id_autopush_state, INT2NUM(state)); } #endif /* IVAR fallback */ static enum autopush_state detect_acceptor_state(VALUE io); static void push_pending_data(VALUE io); /* * call-seq: * Kgio.autopush? -> true or false * * Returns whether or not autopush is enabled. * * Only available on systems with TCP_CORK (Linux) or * TCP_NOPUSH (FreeBSD, and maybe other *BSDs). */ static VALUE s_get_autopush(VALUE self) { return enabled ? Qtrue : Qfalse; } /* * call-seq: * Kgio.autopush = true * Kgio.autopush = false * * Enables or disables autopush for sockets created with kgio_accept * and kgio_tryaccept methods. Autopush relies on TCP_CORK/TCP_NOPUSH * being enabled on the listen socket. * * Only available on systems with TCP_CORK (Linux) or * TCP_NOPUSH (FreeBSD, and maybe other *BSDs). * * Please do not use this (or kgio at all) in new code. Under Linux, * use MSG_MORE, instead, as it requires fewer syscalls. Users of * other systems are encouraged to add MSG_MORE support to their * favorite OS. */ static VALUE s_set_autopush(VALUE self, VALUE val) { enabled = RTEST(val); return val; } /* * call-seq: * * io.kgio_autopush? -> true or false * * Returns the current autopush state of the Kgio::SocketMethods-enabled * socket. * * Only available on systems with TCP_CORK (Linux) or * TCP_NOPUSH (FreeBSD, and maybe other *BSDs). */ static VALUE autopush_get(VALUE io) { return state_get(io) <= 0 ? Qfalse : Qtrue; } /* * call-seq: * * io.kgio_autopush = true * io.kgio_autopush = false * * Enables or disables autopush on any given Kgio::SocketMethods-capable * IO object. This does NOT enable or disable TCP_NOPUSH/TCP_CORK right * away, that must be done with IO.setsockopt * * Only available on systems with TCP_CORK (Linux) or * TCP_NOPUSH (FreeBSD, and maybe other *BSDs). */ static VALUE autopush_set(VALUE io, VALUE vbool) { if (RTEST(vbool)) state_set(io, AUTOPUSH_STATE_WRITER); else state_set(io, AUTOPUSH_STATE_IGNORE); return vbool; } void init_kgio_autopush(void) { VALUE mKgio = rb_define_module("Kgio"); VALUE tmp; rb_define_singleton_method(mKgio, "autopush?", s_get_autopush, 0); rb_define_singleton_method(mKgio, "autopush=", s_set_autopush, 1); tmp = rb_define_module_under(mKgio, "SocketMethods"); rb_define_method(tmp, "kgio_autopush=", autopush_set, 1); rb_define_method(tmp, "kgio_autopush?", autopush_get, 0); id_autopush_state = rb_intern("@kgio_autopush_state"); } /* * called after a successful write, just mark that we've put something * in the skb and will need to uncork on the next write. */ void kgio_autopush_send(VALUE io) { if (state_get(io) == AUTOPUSH_STATE_WRITER) state_set(io, AUTOPUSH_STATE_WRITTEN); } /* called on successful accept() */ void kgio_autopush_accept(VALUE accept_io, VALUE client_io) { enum autopush_state acceptor_state; if (!enabled) return; acceptor_state = state_get(accept_io); if (acceptor_state == AUTOPUSH_STATE_IGNORE) acceptor_state = detect_acceptor_state(accept_io); if (acceptor_state == AUTOPUSH_STATE_ACCEPTOR) state_set(client_io, AUTOPUSH_STATE_WRITER); else state_set(client_io, AUTOPUSH_STATE_IGNORE); } void kgio_autopush_recv(VALUE io) { if (enabled && (state_get(io) == AUTOPUSH_STATE_WRITTEN)) { push_pending_data(io); state_set(io, AUTOPUSH_STATE_WRITER); } } static enum autopush_state detect_acceptor_state(VALUE io) { int corked = 0; int fd = my_fileno(io); socklen_t optlen = sizeof(int); enum autopush_state state; if (getsockopt(fd, IPPROTO_TCP, KGIO_NOPUSH, &corked, &optlen) != 0) { if (errno != EOPNOTSUPP) rb_sys_fail("getsockopt(TCP_CORK/TCP_NOPUSH)"); errno = 0; state = AUTOPUSH_STATE_ACCEPTOR_IGNORE; } else if (corked) { state = AUTOPUSH_STATE_ACCEPTOR; } else { state = AUTOPUSH_STATE_ACCEPTOR_IGNORE; } state_set(io, state); return state; } /* * checks to see if we've written anything since the last recv() * If we have, uncork the socket and immediately recork it. */ static void push_pending_data(VALUE io) { int optval = 0; const socklen_t optlen = sizeof(int); const int fd = my_fileno(io); if (setsockopt(fd, IPPROTO_TCP, KGIO_NOPUSH, &optval, optlen) != 0) rb_sys_fail("setsockopt(TCP_CORK/TCP_NOPUSH, 0)"); /* immediately recork */ optval = 1; if (setsockopt(fd, IPPROTO_TCP, KGIO_NOPUSH, &optval, optlen) != 0) rb_sys_fail("setsockopt(TCP_CORK/TCP_NOPUSH, 1)"); } #else /* !KGIO_NOPUSH */ void kgio_autopush_recv(VALUE io){} void kgio_autopush_send(VALUE io){} void init_kgio_autopush(void) { } #endif /* ! KGIO_NOPUSH */ kgio-2.11.2/ext/kgio/read.c0000644000004100000410000002033313241032461015363 0ustar www-datawww-data/* ref: rubinius b2811f260de16d1e972462e27852470364608de5 */ #define RSTRING_MODIFIED 1 #include "kgio.h" #include "my_fileno.h" #include "nonblock.h" static VALUE sym_wait_readable; #ifdef USE_MSG_DONTWAIT static const int peek_flags = MSG_DONTWAIT|MSG_PEEK; /* we don't need these variants, we call kgio_autopush_recv directly */ static inline void kgio_autopush_read(VALUE io) { } #else static const int peek_flags = MSG_PEEK; static inline void kgio_autopush_read(VALUE io) { kgio_autopush_recv(io); } #endif struct rd_args { VALUE io; VALUE buf; char *ptr; long len; int fd; }; NORETURN(static void my_eof_error(void)); static void my_eof_error(void) { kgio_raise_empty_bt(rb_eEOFError, "end of file reached"); } static void prepare_read(struct rd_args *a, int argc, VALUE *argv, VALUE io) { VALUE length; a->io = io; a->fd = my_fileno(io); rb_scan_args(argc, argv, "11", &length, &a->buf); a->len = NUM2LONG(length); if (NIL_P(a->buf)) { a->buf = rb_str_new(NULL, a->len); } else { StringValue(a->buf); rb_str_modify(a->buf); rb_str_resize(a->buf, a->len); } a->ptr = RSTRING_PTR(a->buf); } static int read_check(struct rd_args *a, long n, const char *msg, int io_wait) { if (n < 0) { if (errno == EINTR) { a->fd = my_fileno(a->io); return -1; } rb_str_set_len(a->buf, 0); if (errno == EAGAIN) { if (io_wait) { (void)kgio_call_wait_readable(a->io); /* buf may be modified in other thread/fiber */ rb_str_modify(a->buf); rb_str_resize(a->buf, a->len); a->ptr = RSTRING_PTR(a->buf); return -1; } else { a->buf = sym_wait_readable; return 0; } } kgio_rd_sys_fail(msg); } rb_str_set_len(a->buf, n); if (n == 0) a->buf = Qnil; return 0; } static VALUE my_read(int io_wait, int argc, VALUE *argv, VALUE io) { struct rd_args a; long n; prepare_read(&a, argc, argv, io); kgio_autopush_read(io); if (a.len > 0) { set_nonblocking(a.fd); retry: n = (long)read(a.fd, a.ptr, a.len); if (read_check(&a, n, "read", io_wait) != 0) goto retry; } return a.buf; } /* * call-seq: * * io.kgio_read(maxlen) -> buffer * io.kgio_read(maxlen, buffer) -> buffer * * Reads at most maxlen bytes from the stream socket. Returns with a * newly allocated buffer, or may reuse an existing buffer if supplied. * * This may block and call any method defined to +kgio_wait_readable+ * for the class. * * Returns nil on EOF. * * This behaves like read(2) and IO#readpartial, NOT fread(3) or * IO#read which possess read-in-full behavior. */ static VALUE kgio_read(int argc, VALUE *argv, VALUE io) { return my_read(1, argc, argv, io); } /* * Same as Kgio::PipeMethods#kgio_read, except EOFError is raised * on EOF without a backtrace. This method is intended as a * drop-in replacement for places where IO#readpartial is used, and * may be aliased as such. */ static VALUE kgio_read_bang(int argc, VALUE *argv, VALUE io) { VALUE rv = my_read(1, argc, argv, io); if (NIL_P(rv)) my_eof_error(); return rv; } /* * call-seq: * * io.kgio_tryread(maxlen) -> buffer * io.kgio_tryread(maxlen, buffer) -> buffer * * Reads at most maxlen bytes from the stream socket. Returns with a * newly allocated buffer, or may reuse an existing buffer if supplied. * * Returns nil on EOF. * * Returns :wait_readable if EAGAIN is encountered. */ static VALUE kgio_tryread(int argc, VALUE *argv, VALUE io) { return my_read(0, argc, argv, io); } #ifdef USE_MSG_DONTWAIT static VALUE my_recv(int io_wait, int argc, VALUE *argv, VALUE io) { struct rd_args a; long n; prepare_read(&a, argc, argv, io); kgio_autopush_recv(io); if (a.len > 0) { retry: n = (long)recv(a.fd, a.ptr, a.len, MSG_DONTWAIT); if (read_check(&a, n, "recv", io_wait) != 0) goto retry; } return a.buf; } /* * This method may be optimized on some systems (e.g. GNU/Linux) to use * MSG_DONTWAIT to avoid explicitly setting the O_NONBLOCK flag via fcntl. * Otherwise this is the same as Kgio::PipeMethods#kgio_read */ static VALUE kgio_recv(int argc, VALUE *argv, VALUE io) { return my_recv(1, argc, argv, io); } /* * Same as Kgio::SocketMethods#kgio_read, except EOFError is raised * on EOF without a backtrace */ static VALUE kgio_recv_bang(int argc, VALUE *argv, VALUE io) { VALUE rv = my_recv(1, argc, argv, io); if (NIL_P(rv)) my_eof_error(); return rv; } /* * This method may be optimized on some systems (e.g. GNU/Linux) to use * MSG_DONTWAIT to avoid explicitly setting the O_NONBLOCK flag via fcntl. * Otherwise this is the same as Kgio::PipeMethods#kgio_tryread */ static VALUE kgio_tryrecv(int argc, VALUE *argv, VALUE io) { return my_recv(0, argc, argv, io); } #else /* ! USE_MSG_DONTWAIT */ # define kgio_recv kgio_read # define kgio_recv_bang kgio_read_bang # define kgio_tryrecv kgio_tryread #endif /* USE_MSG_DONTWAIT */ static VALUE my_peek(int io_wait, int argc, VALUE *argv, VALUE io) { struct rd_args a; long n; prepare_read(&a, argc, argv, io); kgio_autopush_recv(io); if (a.len > 0) { if (peek_flags == MSG_PEEK) set_nonblocking(a.fd); retry: n = (long)recv(a.fd, a.ptr, a.len, peek_flags); if (read_check(&a, n, "recv(MSG_PEEK)", io_wait) != 0) goto retry; } return a.buf; } /* * call-seq: * * socket.kgio_trypeek(maxlen) -> buffer * socket.kgio_trypeek(maxlen, buffer) -> buffer * * Like kgio_tryread, except it uses MSG_PEEK so it does not drain the * socket buffer. A subsequent read of any type (including another peek) * will return the same data. */ static VALUE kgio_trypeek(int argc, VALUE *argv, VALUE io) { return my_peek(0, argc, argv, io); } /* * call-seq: * * socket.kgio_peek(maxlen) -> buffer * socket.kgio_peek(maxlen, buffer) -> buffer * * Like kgio_read, except it uses MSG_PEEK so it does not drain the * socket buffer. A subsequent read of any type (including another peek) * will return the same data. */ static VALUE kgio_peek(int argc, VALUE *argv, VALUE io) { return my_peek(1, argc, argv, io); } /* * call-seq: * * Kgio.trypeek(socket, maxlen) -> buffer * Kgio.trypeek(socket, maxlen, buffer) -> buffer * * Like Kgio.tryread, except it uses MSG_PEEK so it does not drain the * socket buffer. This can only be used on sockets and not pipe objects. * Maybe used in place of SocketMethods#kgio_trypeek for non-Kgio objects */ static VALUE s_trypeek(int argc, VALUE *argv, VALUE mod) { if (argc <= 1) rb_raise(rb_eArgError, "wrong number of arguments"); return my_peek(0, argc - 1, &argv[1], argv[0]); } /* * call-seq: * * Kgio.tryread(io, maxlen) -> buffer * Kgio.tryread(io, maxlen, buffer) -> buffer * * Returns nil on EOF. * Returns :wait_readable if EAGAIN is encountered. * * Maybe used in place of PipeMethods#kgio_tryread for non-Kgio objects */ static VALUE s_tryread(int argc, VALUE *argv, VALUE mod) { if (argc <= 1) rb_raise(rb_eArgError, "wrong number of arguments"); return my_read(0, argc - 1, &argv[1], argv[0]); } void init_kgio_read(void) { VALUE mPipeMethods, mSocketMethods; VALUE mKgio = rb_define_module("Kgio"); sym_wait_readable = ID2SYM(rb_intern("wait_readable")); rb_define_singleton_method(mKgio, "tryread", s_tryread, -1); rb_define_singleton_method(mKgio, "trypeek", s_trypeek, -1); /* * Document-module: Kgio::PipeMethods * * This module may be used used to create classes that respond to * various Kgio methods for reading and writing. This is included * in Kgio::Pipe by default. */ mPipeMethods = rb_define_module_under(mKgio, "PipeMethods"); rb_define_method(mPipeMethods, "kgio_read", kgio_read, -1); rb_define_method(mPipeMethods, "kgio_read!", kgio_read_bang, -1); rb_define_method(mPipeMethods, "kgio_tryread", kgio_tryread, -1); /* * Document-module: Kgio::SocketMethods * * This method behaves like Kgio::PipeMethods, but contains * optimizations for sockets on certain operating systems * (e.g. GNU/Linux). */ mSocketMethods = rb_define_module_under(mKgio, "SocketMethods"); rb_define_method(mSocketMethods, "kgio_read", kgio_recv, -1); rb_define_method(mSocketMethods, "kgio_read!", kgio_recv_bang, -1); rb_define_method(mSocketMethods, "kgio_tryread", kgio_tryrecv, -1); rb_define_method(mSocketMethods, "kgio_trypeek", kgio_trypeek, -1); rb_define_method(mSocketMethods, "kgio_peek", kgio_peek, -1); } kgio-2.11.2/ext/kgio/nonblock.h0000644000004100000410000000065713241032461016271 0ustar www-datawww-data#include #include #include static void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL); /* * do not check < 0 here, one day we may have enough FD flags * to require negative bit */ if (flags == -1) rb_sys_fail("fcntl(F_GETFL)"); if ((flags & O_NONBLOCK) == O_NONBLOCK) return; flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK); if (flags < 0) rb_sys_fail("fcntl(F_SETFL)"); } kgio-2.11.2/ext/kgio/sock_for_fd.h0000644000004100000410000000332413241032461016734 0ustar www-datawww-data#ifndef SOCK_FOR_FD_H #define SOCK_FOR_FD_H #include #ifdef HAVE_RUBY_IO_H # include #else # include # include #endif #if defined(MakeOpenFile) && \ defined(HAVE_RB_IO_T) && (HAVE_RB_IO_T == 1) && \ defined(HAVE_RB_IO_ASCII8BIT_BINMODE) && \ defined(HAVE_ST_FD) && \ defined(HAVE_ST_MODE) # define SOCK_FOR_FD (19) # define FMODE_NOREVLOOKUP 0x100 #elif defined(MakeOpenFile) && \ (defined(OpenFile) || defined(HAVE_RB_IO_T)) && \ defined(HAVE_RB_FDOPEN) && \ defined(HAVE_ST_F) && \ defined(HAVE_ST_F2) && \ defined(HAVE_ST_MODE) # define SOCK_FOR_FD (18) #else # define SOCK_FOR_FD (-1) #endif #if SOCK_FOR_FD == 19 /* modeled after ext/socket/init.c */ static VALUE sock_for_fd(VALUE klass, int fd) { VALUE sock; rb_io_t *fp; rb_update_max_fd(fd); /* 1.9.3+ API */ sock = rb_obj_alloc(klass); MakeOpenFile(sock, fp); fp->fd = fd; fp->mode = FMODE_READWRITE|FMODE_DUPLEX|FMODE_NOREVLOOKUP; rb_io_ascii8bit_binmode(sock); rb_io_synchronized(fp); return sock; } #elif SOCK_FOR_FD == 18 /* modeled after init_sock() in ext/socket/socket.c */ static VALUE sock_for_fd(VALUE klass, int fd) { VALUE sock = rb_obj_alloc(klass); OpenFile *fp; MakeOpenFile(sock, fp); fp->f = rb_fdopen(fd, "r"); fp->f2 = rb_fdopen(fd, "w"); fp->mode = FMODE_READWRITE; rb_io_synchronized(fp); return sock; } #else /* Rubinius, et al. */ static ID id_for_fd; static VALUE sock_for_fd(VALUE klass, int fd) { return rb_funcall(klass, id_for_fd, 1, INT2NUM(fd)); } static void init_sock_for_fd(void) { id_for_fd = rb_intern("for_fd"); } #endif /* sock_for_fd */ #if SOCK_FOR_FD > 0 # define init_sock_for_fd() for (;0;) #endif #endif /* SOCK_FOR_FD_H */ kgio-2.11.2/ext/kgio/broken_system_compat.h0000644000004100000410000000267513241032461020715 0ustar www-datawww-data/* * this header includes functions to support broken systems * without clock_gettime() or CLOCK_MONOTONIC */ #ifndef HAVE_TYPE_CLOCKID_T typedef int clockid_t; #endif #ifndef HAVE_CLOCK_GETTIME # ifndef CLOCK_REALTIME # define CLOCK_REALTIME 0 /* whatever */ # endif static int fake_clock_gettime(clockid_t clk_id, struct timespec *res) { struct timeval tv; int r = gettimeofday(&tv, NULL); assert(0 == r && "gettimeofday() broke!?"); res->tv_sec = tv.tv_sec; res->tv_nsec = tv.tv_usec * 1000; return r; } # define clock_gettime fake_clock_gettime #endif /* broken systems w/o clock_gettime() */ /* * UGH * CLOCK_MONOTONIC is not guaranteed to be a macro, either */ #ifndef CLOCK_MONOTONIC # if (!defined(_POSIX_MONOTONIC_CLOCK) || !defined(HAVE_CLOCK_MONOTONIC)) # define CLOCK_MONOTONIC CLOCK_REALTIME # endif #endif /* * Availability of a monotonic clock needs to be detected at runtime * since we could've been built on a different system than we're run * under. */ static clockid_t hopefully_CLOCK_MONOTONIC; static int check_clock(void) { struct timespec now; hopefully_CLOCK_MONOTONIC = CLOCK_MONOTONIC; /* we can't check this reliably at compile time */ if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) return 1; if (clock_gettime(CLOCK_REALTIME, &now) == 0) { hopefully_CLOCK_MONOTONIC = CLOCK_REALTIME; rb_warn("CLOCK_MONOTONIC not available, " "falling back to CLOCK_REALTIME"); return 2; } return -1; } kgio-2.11.2/ext/kgio/connect.c0000644000004100000410000002562313241032461016110 0ustar www-datawww-data/* We do not modify RSTRING in this file, so RSTRING_MODIFIED is not needed */ #include "kgio.h" #include "my_fileno.h" #include "sock_for_fd.h" #include "blocking_io_region.h" static void close_fail(int fd, const char *msg) { int saved_errno = errno; (void)close(fd); errno = saved_errno; rb_sys_fail(msg); } static int MY_SOCK_STREAM = #if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC) # ifdef HAVE_RB_FD_FIX_CLOEXEC (SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC) # else (SOCK_STREAM|SOCK_NONBLOCK) # endif #else SOCK_STREAM #endif /* ! SOCK_NONBLOCK */ ; /* do not set close-on-exec by default on Ruby <2.0.0 */ #ifndef HAVE_RB_FD_FIX_CLOEXEC # define rb_fd_fix_cloexec(fd) for (;0;) #endif /* HAVE_RB_FD_FIX_CLOEXEC */ /* try to use SOCK_NONBLOCK and SOCK_CLOEXEC */ static int my_socket(int domain) { int fd; retry: fd = socket(domain, MY_SOCK_STREAM, 0); if (fd < 0) { switch (errno) { case EMFILE: case ENFILE: #ifdef ENOBUFS case ENOBUFS: #endif /* ENOBUFS */ errno = 0; rb_gc(); fd = socket(domain, MY_SOCK_STREAM, 0); break; case EINVAL: if (MY_SOCK_STREAM != SOCK_STREAM) { MY_SOCK_STREAM = SOCK_STREAM; goto retry; } } if (fd < 0) rb_sys_fail("socket"); } if (MY_SOCK_STREAM == SOCK_STREAM) { if (fcntl(fd, F_SETFL, O_RDWR | O_NONBLOCK) < 0) close_fail(fd, "fcntl(F_SETFL, O_RDWR | O_NONBLOCK)"); rb_fd_fix_cloexec(fd); } return fd; } static VALUE my_connect(VALUE klass, int io_wait, int domain, const void *addr, socklen_t addrlen) { int fd = my_socket(domain); if (connect(fd, addr, addrlen) < 0) { if (errno == EINPROGRESS) { VALUE io = sock_for_fd(klass, fd); if (io_wait) { errno = EAGAIN; (void)kgio_call_wait_writable(io); } return io; } close_fail(fd, "connect"); } return sock_for_fd(klass, fd); } static void tcp_getaddr(struct addrinfo *hints, struct sockaddr_storage *addr, VALUE ip, VALUE port) { int rc; struct addrinfo *res; const char *ipname = StringValuePtr(ip); char ipport[6]; unsigned uport; if (TYPE(port) != T_FIXNUM) rb_raise(rb_eTypeError, "port must be a non-negative integer"); uport = FIX2UINT(port); rc = snprintf(ipport, sizeof(ipport), "%u", uport); if (rc >= (int)sizeof(ipport) || rc <= 0) rb_raise(rb_eArgError, "invalid TCP port: %u", uport); memset(hints, 0, sizeof(struct addrinfo)); hints->ai_family = AF_UNSPEC; hints->ai_socktype = SOCK_STREAM; hints->ai_protocol = IPPROTO_TCP; /* disallow non-deterministic DNS lookups */ hints->ai_flags = AI_NUMERICHOST; rc = getaddrinfo(ipname, ipport, hints, &res); if (rc != 0) rb_raise(rb_eArgError, "getaddrinfo(%s:%s): %s", ipname, ipport, gai_strerror(rc)); /* copy needed data and free ASAP to avoid needing rb_ensure */ hints->ai_family = res->ai_family; hints->ai_addrlen = res->ai_addrlen; memcpy(addr, res->ai_addr, res->ai_addrlen); freeaddrinfo(res); } static VALUE tcp_connect(VALUE klass, VALUE ip, VALUE port, int io_wait) { struct addrinfo hints; struct sockaddr_storage addr; tcp_getaddr(&hints, &addr, ip, port); return my_connect(klass, io_wait, hints.ai_family, &addr, hints.ai_addrlen); } static const struct sockaddr *sockaddr_from(socklen_t *addrlen, VALUE addr) { if (TYPE(addr) == T_STRING) { *addrlen = (socklen_t)RSTRING_LEN(addr); return (const struct sockaddr *)(RSTRING_PTR(addr)); } rb_raise(rb_eTypeError, "invalid address"); return NULL; } #if defined(MSG_FASTOPEN) && defined(KGIO_WITHOUT_GVL) struct tfo_args { int fd; const void *buf; size_t buflen; const struct sockaddr *addr; socklen_t addrlen; }; static VALUE tfo_sendto(void *_a) { struct tfo_args *a = _a; ssize_t w; w = sendto(a->fd, a->buf, a->buflen, MSG_FASTOPEN, a->addr, a->addrlen); return (VALUE)w; } /* * call-seq: * * s = Kgio::Socket.new(:INET, :STREAM) * addr = Socket.pack_sockaddr_in(80, "example.com") * s.kgio_fastopen("hello world", addr) -> nil * * Starts a TCP connection using TCP Fast Open. This uses a blocking * sendto() syscall and is only available on Ruby 1.9 or later. * This raises exceptions (including Errno::EINPROGRESS/Errno::EAGAIN) * on errors. Using this is only recommended for blocking sockets. * * Timeouts may be set with setsockopt: * * s.setsockopt(:SOCKET, :SNDTIMEO, [1,0].pack("l_l_")) */ static VALUE fastopen(VALUE sock, VALUE buf, VALUE addr) { struct tfo_args a; VALUE str = (TYPE(buf) == T_STRING) ? buf : rb_obj_as_string(buf); ssize_t w; a.fd = my_fileno(sock); a.buf = RSTRING_PTR(str); a.buflen = (size_t)RSTRING_LEN(str); a.addr = sockaddr_from(&a.addrlen, addr); /* n.b. rb_thread_blocking_region preserves errno */ w = (ssize_t)rb_thread_io_blocking_region(tfo_sendto, &a, a.fd); if (w < 0) rb_sys_fail("sendto"); if ((size_t)w == a.buflen) return Qnil; return MY_STR_SUBSEQ(str, w, a.buflen - w); } #endif /* MSG_FASTOPEN */ /* * call-seq: * * Kgio::TCPSocket.new('127.0.0.1', 80) -> socket * * Creates a new Kgio::TCPSocket object and initiates a * non-blocking connection. * * This may block and call any method defined to +kgio_wait_writable+ * for the class. * * Unlike the TCPSocket.new in Ruby, this does NOT perform DNS * lookups (which is subject to a different set of timeouts and * best handled elsewhere). */ static VALUE kgio_tcp_connect(VALUE klass, VALUE ip, VALUE port) { return tcp_connect(klass, ip, port, 1); } /* * call-seq: * * Kgio::TCPSocket.start('127.0.0.1', 80) -> socket * * Creates a new Kgio::TCPSocket object and initiates a * non-blocking connection. The caller should select/poll * on the socket for writability before attempting to write * or optimistically attempt a write and handle :wait_writable * or Errno::EAGAIN. * * Unlike the TCPSocket.new in Ruby, this does NOT perform DNS * lookups (which is subject to a different set of timeouts and * best handled elsewhere). */ static VALUE kgio_tcp_start(VALUE klass, VALUE ip, VALUE port) { return tcp_connect(klass, ip, port, 0); } static VALUE unix_connect(VALUE klass, VALUE path, int io_wait) { struct sockaddr_un addr = { 0 }; long len; StringValue(path); len = RSTRING_LEN(path); if ((long)sizeof(addr.sun_path) <= len) rb_raise(rb_eArgError, "too long unix socket path (max: %dbytes)", (int)sizeof(addr.sun_path)-1); memcpy(addr.sun_path, RSTRING_PTR(path), len); addr.sun_family = AF_UNIX; return my_connect(klass, io_wait, PF_UNIX, &addr, sizeof(addr)); } /* * call-seq: * * Kgio::UNIXSocket.new("/path/to/unix/socket") -> socket * * Creates a new Kgio::UNIXSocket object and initiates a * non-blocking connection. * * This may block and call any method defined to +kgio_wait_writable+ * for the class. */ static VALUE kgio_unix_connect(VALUE klass, VALUE path) { return unix_connect(klass, path, 1); } /* * call-seq: * * Kgio::UNIXSocket.start("/path/to/unix/socket") -> socket * * Creates a new Kgio::UNIXSocket object and initiates a * non-blocking connection. The caller should select/poll * on the socket for writability before attempting to write * or optimistically attempt a write and handle :wait_writable * or Errno::EAGAIN. */ static VALUE kgio_unix_start(VALUE klass, VALUE path) { return unix_connect(klass, path, 0); } static VALUE stream_connect(VALUE klass, VALUE addr, int io_wait) { int domain; socklen_t addrlen; const struct sockaddr *sockaddr = sockaddr_from(&addrlen, addr); switch (((const struct sockaddr_storage *)(sockaddr))->ss_family) { case AF_UNIX: domain = PF_UNIX; break; case AF_INET: domain = PF_INET; break; case AF_INET6: domain = PF_INET6; break; default: rb_raise(rb_eArgError, "invalid address family"); } return my_connect(klass, io_wait, domain, sockaddr, addrlen); } /* call-seq: * * addr = Socket.pack_sockaddr_in(80, 'example.com') * Kgio::Socket.connect(addr) -> socket * * addr = Socket.pack_sockaddr_un("/path/to/unix/socket") * Kgio::Socket.connect(addr) -> socket * * Creates a generic Kgio::Socket object and initiates a * non-blocking connection. * * This may block and call any method defined to +kgio_wait_writable+ * for the class. */ static VALUE kgio_connect(VALUE klass, VALUE addr) { return stream_connect(klass, addr, 1); } /* * If passed one argument, this is identical to Kgio::Socket.connect. * If passed two or three arguments, it uses its superclass method: * * Socket.new(domain, socktype [, protocol ]) */ static VALUE kgio_new(int argc, VALUE *argv, VALUE klass) { if (argc == 1) /* backwards compat, the only way for kgio <= 2.7.4 */ return stream_connect(klass, argv[0], 1); return rb_call_super(argc, argv); } /* call-seq: * * addr = Socket.pack_sockaddr_in(80, 'example.com') * Kgio::Socket.start(addr) -> socket * * addr = Socket.pack_sockaddr_un("/path/to/unix/socket") * Kgio::Socket.start(addr) -> socket * * Creates a generic Kgio::Socket object and initiates a * non-blocking connection. The caller should select/poll * on the socket for writability before attempting to write * or optimistically attempt a write and handle :wait_writable * or Errno::EAGAIN. */ static VALUE kgio_start(VALUE klass, VALUE addr) { return stream_connect(klass, addr, 0); } void init_kgio_connect(void) { VALUE mKgio = rb_define_module("Kgio"); VALUE cSocket = rb_const_get(rb_cObject, rb_intern("Socket")); VALUE mSocketMethods = rb_const_get(mKgio, rb_intern("SocketMethods")); VALUE cKgio_Socket, cTCPSocket, cUNIXSocket; /* * Document-class: Kgio::Socket * * A generic socket class with Kgio::SocketMethods included. * This is returned by all Kgio methods that accept(2) a connected * stream socket. */ cKgio_Socket = rb_define_class_under(mKgio, "Socket", cSocket); rb_include_module(cKgio_Socket, mSocketMethods); rb_define_singleton_method(cKgio_Socket, "new", kgio_new, -1); rb_define_singleton_method(cKgio_Socket, "connect", kgio_connect, 1); rb_define_singleton_method(cKgio_Socket, "start", kgio_start, 1); #if defined(MSG_FASTOPEN) && defined(KGIO_WITHOUT_GVL) rb_define_method(cKgio_Socket, "kgio_fastopen", fastopen, 2); #endif /* * Document-class: Kgio::TCPSocket * * Kgio::TCPSocket should be used in place of the plain TCPSocket * when kgio_* methods are needed. */ cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket")); cTCPSocket = rb_define_class_under(mKgio, "TCPSocket", cTCPSocket); rb_include_module(cTCPSocket, mSocketMethods); rb_define_singleton_method(cTCPSocket, "new", kgio_tcp_connect, 2); rb_define_singleton_method(cTCPSocket, "start", kgio_tcp_start, 2); /* * Document-class: Kgio::UNIXSocket * * Kgio::UNIXSocket should be used in place of the plain UNIXSocket * when kgio_* methods are needed. */ cUNIXSocket = rb_const_get(rb_cObject, rb_intern("UNIXSocket")); cUNIXSocket = rb_define_class_under(mKgio, "UNIXSocket", cUNIXSocket); rb_include_module(cUNIXSocket, mSocketMethods); rb_define_singleton_method(cUNIXSocket, "new", kgio_unix_connect, 1); rb_define_singleton_method(cUNIXSocket, "start", kgio_unix_start, 1); init_sock_for_fd(); } kgio-2.11.2/ext/kgio/kgio_ext.c0000644000004100000410000000475013241032461016266 0ustar www-datawww-data#include "kgio.h" #include #include /* true if TCP Fast Open is usable */ unsigned kgio_tfo; static VALUE eErrno_EPIPE, eErrno_ECONNRESET; static ID id_set_backtrace; static void tfo_maybe(void) { VALUE mKgio = rb_define_module("Kgio"); /* Deal with the case where system headers have not caught up */ if (KGIO_TFO_MAYBE) { /* Ensure Linux 3.7 or later for TCP_FASTOPEN */ struct utsname buf; unsigned maj, min; if (uname(&buf) != 0) rb_sys_fail("uname"); if (sscanf(buf.release, "%u.%u", &maj, &min) != 2) return; if (maj < 3 || (maj == 3 && min < 7)) return; } /* * KGIO_TFO_MAYBE will be false if a distro backports TFO * to a pre-3.7 kernel, but includes the necessary constants * in system headers */ #if defined(MSG_FASTOPEN) && defined(TCP_FASTOPEN) rb_define_const(mKgio, "TCP_FASTOPEN", INT2NUM(TCP_FASTOPEN)); rb_define_const(mKgio, "MSG_FASTOPEN", INT2NUM(MSG_FASTOPEN)); kgio_tfo = 1; #endif } void kgio_raise_empty_bt(VALUE err, const char *msg) { VALUE exc = rb_exc_new2(err, msg); VALUE bt = rb_ary_new(); rb_funcall(exc, id_set_backtrace, 1, bt); rb_exc_raise(exc); } void kgio_wr_sys_fail(const char *msg) { switch (errno) { case EPIPE: errno = 0; kgio_raise_empty_bt(eErrno_EPIPE, msg); case ECONNRESET: errno = 0; kgio_raise_empty_bt(eErrno_ECONNRESET, msg); } rb_sys_fail(msg); } void kgio_rd_sys_fail(const char *msg) { if (errno == ECONNRESET) { errno = 0; kgio_raise_empty_bt(eErrno_ECONNRESET, msg); } rb_sys_fail(msg); } void Init_kgio_ext(void) { VALUE mKgio = rb_define_module("Kgio"); VALUE mPipeMethods = rb_define_module_under(mKgio, "PipeMethods"); VALUE mSocketMethods = rb_define_module_under(mKgio, "SocketMethods"); VALUE mWaiters = rb_define_module_under(mKgio, "DefaultWaiters"); id_set_backtrace = rb_intern("set_backtrace"); eErrno_EPIPE = rb_const_get(rb_mErrno, rb_intern("EPIPE")); eErrno_ECONNRESET = rb_const_get(rb_mErrno, rb_intern("ECONNRESET")); /* * Returns the client IP address of the socket as a string * (e.g. "127.0.0.1" or "::1"). * This is always the value of the Kgio::LOCALHOST constant * for UNIX domain sockets. */ rb_define_attr(mSocketMethods, "kgio_addr", 1, 1); rb_include_module(mPipeMethods, mWaiters); rb_include_module(mSocketMethods, mWaiters); tfo_maybe(); init_kgio_wait(); init_kgio_read(); init_kgio_write(); init_kgio_writev(); init_kgio_connect(); init_kgio_accept(); init_kgio_autopush(); init_kgio_poll(); init_kgio_tryopen(); } kgio-2.11.2/ext/kgio/poll.c0000644000004100000410000001317413241032461015423 0ustar www-datawww-data#include "kgio.h" #if defined(USE_KGIO_POLL) #include "my_fileno.h" #include #include "broken_system_compat.h" #include #include #ifdef HAVE_RUBY_ST_H # include #else # include #endif static VALUE sym_wait_readable, sym_wait_writable; #ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */ # define my_hash_clear(h) (void)rb_hash_clear(h) #else /* !HAVE_RB_HASH_CLEAR - Ruby <= 1.9.3 */ static ID id_clear; static void my_hash_clear(VALUE h) { rb_funcall(h, id_clear, 0); } #endif /* HAVE_RB_HASH_CLEAR */ struct poll_args { struct pollfd *fds; nfds_t nfds; int timeout; VALUE ios; st_table *fd_to_io; struct timespec start; }; static int interrupted(void) { switch (errno) { case EINTR: #ifdef ERESTART case ERESTART: #endif return 1; } return 0; } static int retryable(struct poll_args *a) { struct timespec ts; if (a->timeout < 0) return 1; clock_gettime(hopefully_CLOCK_MONOTONIC, &ts); ts.tv_sec -= a->start.tv_sec; ts.tv_nsec -= a->start.tv_nsec; if (ts.tv_nsec < 0) { ts.tv_sec--; ts.tv_nsec += 1000000000; } a->timeout -= ts.tv_sec * 1000; a->timeout -= ts.tv_nsec / 1000000; if (a->timeout < 0) a->timeout = 0; return 1; } static int num2timeout(VALUE timeout) { switch (TYPE(timeout)) { case T_NIL: return -1; case T_FIXNUM: return FIX2INT(timeout); case T_BIGNUM: return NUM2INT(timeout); } rb_raise(rb_eTypeError, "timeout must be integer or nil"); return 0; } static VALUE poll_free(VALUE args) { struct poll_args *a = (struct poll_args *)args; if (a->fds) xfree(a->fds); if (a->fd_to_io) st_free_table(a->fd_to_io); return Qnil; } static short value2events(VALUE event) { if (event == sym_wait_readable) return POLLIN; if (event == sym_wait_writable) return POLLOUT; if (TYPE(event) == T_FIXNUM) return (short)FIX2INT(event); rb_raise(rb_eArgError, "unrecognized event"); } static int io_to_pollfd_i(VALUE key, VALUE value, VALUE args) { struct poll_args *a = (struct poll_args *)args; struct pollfd *pollfd = &a->fds[a->nfds++]; pollfd->fd = my_fileno(key); pollfd->events = value2events(value); st_insert(a->fd_to_io, (st_data_t)pollfd->fd, (st_data_t)key); return ST_CONTINUE; } static void hash2pollfds(struct poll_args *a) { a->nfds = 0; a->fds = xmalloc(sizeof(struct pollfd) * RHASH_SIZE(a->ios)); a->fd_to_io = st_init_numtable(); rb_hash_foreach(a->ios, io_to_pollfd_i, (VALUE)a); } static void * nogvl_poll(void *ptr) { struct poll_args *a = ptr; long n; if (a->timeout > 0) clock_gettime(hopefully_CLOCK_MONOTONIC, &a->start); n = poll(a->fds, a->nfds, a->timeout); return (void *)n; } static VALUE poll_result(int nr, struct poll_args *a) { struct pollfd *fds = a->fds; VALUE io; int rc; if ((nfds_t)nr != a->nfds) my_hash_clear(a->ios); for (; nr > 0; fds++) { if (fds->revents == 0) continue; --nr; rc = st_lookup(a->fd_to_io, (st_data_t)fds->fd, &io); assert(rc == 1 && "fd => IO mapping failed"); rb_hash_aset(a->ios, io, INT2FIX((int)fds->revents)); } return a->ios; } static VALUE do_poll(VALUE args) { struct poll_args *a = (struct poll_args *)args; long nr; Check_Type(a->ios, T_HASH); retry: hash2pollfds(a); nr = (long)KGIO_WITHOUT_GVL(nogvl_poll, a, RUBY_UBF_IO, NULL); if (nr < 0) { if (interrupted()) { if (retryable(a)) { poll_free(args); goto retry; } return Qnil; } rb_sys_fail("poll"); } if (nr == 0) return Qnil; return poll_result((int)nr, a); } /* * call-seq: * * Kgio.poll({ $stdin => :wait_readable }, 100) -> hash or nil * Kgio.poll({ $stdin => Kgio::POLLIN }, 100) -> hash or nil * * Accepts an input hash with IO objects to wait for as the key and * the events to wait for as its value. The events may either be * +:wait_readable+ or +:wait_writable+ symbols or a Fixnum mask of * Kgio::POLL* constants: * * Kgio::POLLIN - there is data to read * Kgio::POLLPRI - there is urgent data to read * Kgio::POLLOUT - writing will not block * Kgio::POLLRDHUP - peer has shutdown writes (Linux 2.6.17+ only) * * Timeout is specified in Integer milliseconds just like the underlying * poll(2), not in seconds like IO.select. A nil timeout means to wait * forever. It must be an Integer or nil. * * Kgio.poll modifies and returns its input hash on success with the * IO-like object as the key and an Integer mask of events as the hash * value. It can return any of the events specified in the input * above, along with the following events: * * Kgio::POLLERR - error condition occurred on the descriptor * Kgio::POLLHUP - hang up * Kgio::POLLNVAL - invalid request (bad file descriptor) * * This method is only available under Ruby 1.9 or any other * implementations that uses native threads and rb_thread_blocking_region() */ static VALUE s_poll(int argc, VALUE *argv, VALUE self) { VALUE timeout; struct poll_args a; rb_scan_args(argc, argv, "11", &a.ios, &timeout); a.timeout = num2timeout(timeout); a.fds = NULL; a.fd_to_io = NULL; return rb_ensure(do_poll, (VALUE)&a, poll_free, (VALUE)&a); } void init_kgio_poll(void) { VALUE mKgio = rb_define_module("Kgio"); if (check_clock() < 0) return; rb_define_singleton_method(mKgio, "poll", s_poll, -1); sym_wait_readable = ID2SYM(rb_intern("wait_readable")); sym_wait_writable = ID2SYM(rb_intern("wait_writable")); #ifndef HAVE_RB_HASH_CLEAR id_clear = rb_intern("clear"); #endif #define c(x) rb_define_const(mKgio,#x,INT2NUM((int)x)) /* standard types */ c(POLLIN); c(POLLPRI); c(POLLOUT); #ifdef POLLRDHUP c(POLLRDHUP); #endif /* outputs */ c(POLLERR); c(POLLHUP); c(POLLNVAL); } #else /* ! USE_KGIO_POLL */ void init_kgio_poll(void) { } #endif /* ! USE_KGIO_POLL */ kgio-2.11.2/GIT-VERSION-GEN0000755000004100000410000000133513241032461014657 0ustar www-datawww-data#!/bin/sh GVF=GIT-VERSION-FILE DEF_VER=v2.11.2 LF=' ' # First see if there is a version file (included in release tarballs), # then try git-describe, then default. if test -f version then VN=$(cat version) || VN="$DEF_VER" elif test -d .git -o -f .git && VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; v[0-9]*) git update-index -q --refresh test -z "$(git diff-index --name-only HEAD --)" || VN="$VN-dirty" ;; esac then VN=$(echo "$VN" | sed -e 's/-/./g'); else VN="$DEF_VER" fi VN=$(expr "$VN" : v*'\(.*\)') if test -r $GVF then VC=$(sed -e 's/^GIT_VERSION = //' <$GVF) else VC=unset fi test "$VN" = "$VC" || { echo >&2 "GIT_VERSION = $VN" echo "GIT_VERSION = $VN" >$GVF } kgio-2.11.2/.document0000644000004100000410000000031113241032461014364 0ustar www-datawww-dataLICENSE README TODO NEWS LATEST ISSUES HACKING lib ext/kgio/accept.c ext/kgio/autopush.c ext/kgio/connect.c ext/kgio/kgio_ext.c ext/kgio/poll.c ext/kgio/read_write.c ext/kgio/wait.c ext/kgio/tryopen.c kgio-2.11.2/COPYING0000644000004100000410000001672513241032461013620 0ustar www-datawww-data GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.