diff options
-rw-r--r-- | NEWS.md | 9 | ||||
-rw-r--r-- | configure.ac | 5 | ||||
-rw-r--r-- | ext/socket/extconf.rb | 1 | ||||
-rw-r--r-- | ext/socket/init.c | 2 | ||||
-rw-r--r-- | ext/socket/lib/socket.rb | 2 | ||||
-rw-r--r-- | ext/socket/option.c | 10 | ||||
-rw-r--r-- | ext/socket/raddrinfo.c | 20 | ||||
-rw-r--r-- | ext/socket/rubysocket.h | 12 | ||||
-rw-r--r-- | ext/socket/socket.c | 6 | ||||
-rw-r--r-- | ext/socket/unixserver.c | 4 | ||||
-rw-r--r-- | ext/socket/unixsocket.c | 8 | ||||
-rw-r--r-- | file.c | 3 | ||||
-rw-r--r-- | include/ruby/win32.h | 6 | ||||
-rw-r--r-- | test/fiber/test_enumerator.rb | 6 | ||||
-rw-r--r-- | test/fiber/test_io.rb | 23 | ||||
-rw-r--r-- | test/fileutils/test_fileutils.rb | 4 | ||||
-rw-r--r-- | test/ruby/test_file_exhaustive.rb | 2 | ||||
-rw-r--r-- | test/ruby/test_io.rb | 24 | ||||
-rw-r--r-- | test/ruby/test_io_timeout.rb | 6 | ||||
-rw-r--r-- | test/socket/test_nonblock.rb | 4 | ||||
-rw-r--r-- | test/socket/test_unix.rb | 147 | ||||
-rw-r--r-- | win32/Makefile.sub | 3 | ||||
-rw-r--r-- | win32/file.h | 4 | ||||
-rw-r--r-- | win32/win32.c | 154 |
24 files changed, 340 insertions, 125 deletions
@@ -103,7 +103,8 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. * Fiber::Scheduler - * Introduce `Fiber::Scheduler#io_select` for non-blocking `IO.select`. [[Feature #19060]] + * Introduce `Fiber::Scheduler#io_select` for non-blocking `IO.select`. + [[Feature #19060]] * IO * Introduce `IO#timeout=` and `IO#timeout` which can cause @@ -115,6 +116,11 @@ Note: We're only listing outstanding class updates. STDIN.read # => Blocking operation timed out! (IO::TimeoutError) ``` +* UNIXSocket + * Add support for UNIXSocket on Windows. Emulate anonymous sockets. Add + support for `File.socket?` and `File::Stat#socket?` where possible. + [[Feature #19135]] + * Class * `Class#attached_object`, which returns the object for which the receiver is the singleton class. Raises `TypeError` if the @@ -417,3 +423,4 @@ The following deprecated APIs are removed. [Feature #19026]: https://bugs.ruby-lang.org/issues/19026 [Feature #19060]: https://bugs.ruby-lang.org/issues/19060 [Bug #19100]: https://bugs.ruby-lang.org/issues/19100 +[Feature #19135]: https://bugs.ruby-lang.org/issues/19135 diff --git a/configure.ac b/configure.ac index f6b81c4c15..d11aae2170 100644 --- a/configure.ac +++ b/configure.ac @@ -1283,6 +1283,11 @@ dnl AC_HEADER_STDC has been checked in AC_USE_SYSTEM_EXTENSIONS AC_HEADER_STDBOOL AC_HEADER_SYS_WAIT +AC_CHECK_HEADERS([afunix.h], [], [], +[#ifdef _WIN32 +# include <winsock2.h> +#endif +]) AC_CHECK_HEADERS(atomic.h) AC_CHECK_HEADERS(copyfile.h) AC_CHECK_HEADERS(direct.h) diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb index 8998bb5c2f..73bbc8e687 100644 --- a/ext/socket/extconf.rb +++ b/ext/socket/extconf.rb @@ -316,6 +316,7 @@ end netpacket/packet.h net/ethernet.h sys/un.h + afunix.h ifaddrs.h sys/ioctl.h sys/sockio.h diff --git a/ext/socket/init.c b/ext/socket/init.c index ac28f5c329..557d4374a5 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -209,7 +209,7 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from) else return rb_assoc_new(str, Qnil); -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case RECV_UNIX: return rb_assoc_new(str, rsock_unixaddr(&arg.buf.un, arg.alen)); #endif diff --git a/ext/socket/lib/socket.rb b/ext/socket/lib/socket.rb index d756a32a5a..c96e48e8f0 100644 --- a/ext/socket/lib/socket.rb +++ b/ext/socket/lib/socket.rb @@ -197,7 +197,7 @@ class Addrinfo sock = Socket.new(self.pfamily, self.socktype, self.protocol) begin sock.ipv6only! if self.ipv6? - sock.setsockopt(:SOCKET, :REUSEADDR, 1) + sock.setsockopt(:SOCKET, :REUSEADDR, 1) unless self.pfamily == Socket::PF_UNIX sock.bind(self) sock.listen(backlog) rescue Exception diff --git a/ext/socket/option.c b/ext/socket/option.c index 2dbe6379c4..0d818d0c70 100644 --- a/ext/socket/option.c +++ b/ext/socket/option.c @@ -670,10 +670,10 @@ rb_if_indextoname(const char *succ_prefix, const char *fail_prefix, unsigned int { #if defined(HAVE_IF_INDEXTONAME) char ifbuf[IFNAMSIZ]; - if (if_indextoname(ifindex, ifbuf) == NULL) - return snprintf(buf, len, "%s%u", fail_prefix, ifindex); - else + if (if_indextoname(ifindex, ifbuf)) return snprintf(buf, len, "%s%s", succ_prefix, ifbuf); + else + return snprintf(buf, len, "%s%u", fail_prefix, ifindex); #else # ifndef IFNAMSIZ # define IFNAMSIZ (sizeof(unsigned int)*3+1) @@ -1229,7 +1229,7 @@ sockopt_inspect(VALUE self) else rb_str_catf(ret, " optname:%d", optname); } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN else if (family == AF_UNIX) { rb_str_catf(ret, " level:%d", level); @@ -1393,7 +1393,7 @@ sockopt_inspect(VALUE self) } break; -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: switch (level) { case 0: diff --git a/ext/socket/raddrinfo.c b/ext/socket/raddrinfo.c index 636d1edda3..269edc4dad 100644 --- a/ext/socket/raddrinfo.c +++ b/ext/socket/raddrinfo.c @@ -644,7 +644,7 @@ rsock_ipaddr(struct sockaddr *sockaddr, socklen_t sockaddrlen, int norevlookup) return ary; } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN static long unixsocket_len(const struct sockaddr_un *su, socklen_t socklen) { @@ -1017,7 +1017,7 @@ addrinfo_list_new(VALUE node, VALUE service, VALUE family, VALUE socktype, VALUE } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN static void init_unix_addrinfo(rb_addrinfo_t *rai, VALUE path, int socktype) { @@ -1146,7 +1146,7 @@ addrinfo_initialize(int argc, VALUE *argv, VALUE self) break; } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: /* ["AF_UNIX", "/tmp/sock"] */ { VALUE path = rb_ary_entry(sockaddr_ary, 1); @@ -1286,7 +1286,7 @@ rsock_inspect_sockaddr(struct sockaddr *sockaddr_arg, socklen_t socklen, VALUE r } #endif -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: { struct sockaddr_un *addr = &sockaddr->un; @@ -1622,7 +1622,7 @@ addrinfo_mdump(VALUE self) afamily = rb_id2str(id); switch(afamily_int) { -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: { sockaddr = rb_str_new(rai->addr.un.sun_path, rai_unixsocket_len(rai)); @@ -1715,7 +1715,7 @@ addrinfo_mload(VALUE self, VALUE ary) v = rb_ary_entry(ary, 1); switch(afamily) { -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: { struct sockaddr_un uaddr; @@ -2343,7 +2343,7 @@ addrinfo_ipv6_to_ipv4(VALUE self) #endif -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * call-seq: * addrinfo.unix_path => path @@ -2491,7 +2491,7 @@ addrinfo_s_udp(VALUE self, VALUE host, VALUE port) INT2NUM(PF_UNSPEC), INT2NUM(SOCK_DGRAM), INT2NUM(IPPROTO_UDP), INT2FIX(0)); } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * call-seq: @@ -2629,7 +2629,7 @@ rsock_init_addrinfo(void) rb_define_singleton_method(rb_cAddrinfo, "ip", addrinfo_s_ip, 1); rb_define_singleton_method(rb_cAddrinfo, "tcp", addrinfo_s_tcp, 2); rb_define_singleton_method(rb_cAddrinfo, "udp", addrinfo_s_udp, 2); -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN rb_define_singleton_method(rb_cAddrinfo, "unix", addrinfo_s_unix, -1); #endif @@ -2670,7 +2670,7 @@ rsock_init_addrinfo(void) rb_define_method(rb_cAddrinfo, "ipv6_to_ipv4", addrinfo_ipv6_to_ipv4, 0); #endif -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN rb_define_method(rb_cAddrinfo, "unix_path", addrinfo_unix_path, 0); #endif diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 9ec893ee8c..5f803ba0da 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -33,6 +33,9 @@ #endif #ifdef _WIN32 +# include <winsock2.h> +# include <ws2tcpip.h> +# include <iphlpapi.h> # if defined(_MSC_VER) # undef HAVE_TYPE_STRUCT_SOCKADDR_DL # endif @@ -69,6 +72,11 @@ # include <sys/un.h> #endif +#ifdef HAVE_AFUNIX_H +// Windows doesn't have sys/un.h, but it does have afunix.h just to be special: +# include <afunix.h> +#endif + #if defined(HAVE_FCNTL) # ifdef HAVE_SYS_SELECT_H # include <sys/select.h> @@ -268,7 +276,7 @@ extern VALUE rb_cIPSocket; extern VALUE rb_cTCPSocket; extern VALUE rb_cTCPServer; extern VALUE rb_cUDPSocket; -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN extern VALUE rb_cUNIXSocket; extern VALUE rb_cUNIXServer; #endif @@ -336,7 +344,7 @@ VALUE rsock_sockaddr_obj(struct sockaddr *addr, socklen_t len); int rsock_revlookup_flag(VALUE revlookup, int *norevlookup); -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN VALUE rsock_unixpath_str(struct sockaddr_un *sockaddr, socklen_t len); VALUE rsock_unixaddr(struct sockaddr_un *sockaddr, socklen_t len); socklen_t rsock_unix_sockaddr_len(VALUE path); diff --git a/ext/socket/socket.c b/ext/socket/socket.c index 5cf0835062..eb74f7a936 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -1383,7 +1383,7 @@ sock_s_unpack_sockaddr_in(VALUE self, VALUE addr) return rb_assoc_new(INT2NUM(ntohs(sockaddr->sin_port)), host); } -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * call-seq: @@ -1471,7 +1471,7 @@ sockaddr_len(struct sockaddr *addr) return (socklen_t)sizeof(struct sockaddr_in6); #endif -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN case AF_UNIX: return (socklen_t)sizeof(struct sockaddr_un); #endif @@ -2020,7 +2020,7 @@ Init_socket(void) rb_define_singleton_method(rb_cSocket, "sockaddr_in", sock_s_pack_sockaddr_in, 2); rb_define_singleton_method(rb_cSocket, "pack_sockaddr_in", sock_s_pack_sockaddr_in, 2); rb_define_singleton_method(rb_cSocket, "unpack_sockaddr_in", sock_s_unpack_sockaddr_in, 1); -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN rb_define_singleton_method(rb_cSocket, "sockaddr_un", sock_s_pack_sockaddr_un, 1); rb_define_singleton_method(rb_cSocket, "pack_sockaddr_un", sock_s_pack_sockaddr_un, 1); rb_define_singleton_method(rb_cSocket, "unpack_sockaddr_un", sock_s_unpack_sockaddr_un, 1); diff --git a/ext/socket/unixserver.c b/ext/socket/unixserver.c index 3a899cca1f..0ea5ac083c 100644 --- a/ext/socket/unixserver.c +++ b/ext/socket/unixserver.c @@ -10,7 +10,7 @@ #include "rubysocket.h" -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * call-seq: * UNIXServer.new(path) => unixserver @@ -101,7 +101,7 @@ unix_sysaccept(VALUE server) void rsock_init_unixserver(void) { -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * Document-class: UNIXServer < UNIXSocket * diff --git a/ext/socket/unixsocket.c b/ext/socket/unixsocket.c index ecffbd4e92..26ab76fc9f 100644 --- a/ext/socket/unixsocket.c +++ b/ext/socket/unixsocket.c @@ -10,7 +10,7 @@ #include "rubysocket.h" -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN struct unixsock_arg { struct sockaddr_un *sockaddr; socklen_t sockaddrlen; @@ -43,6 +43,10 @@ unixsock_path_value(VALUE path) } } #endif +#ifdef _WIN32 + /* UNIXSocket requires UTF-8 per spec. */ + path = rb_str_export_to_enc(path, rb_utf8_encoding()); +#endif return rb_get_path(path); } @@ -571,7 +575,7 @@ unix_s_socketpair(int argc, VALUE *argv, VALUE klass) void rsock_init_unixsocket(void) { -#ifdef HAVE_SYS_UN_H +#ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN /* * Document-class: UNIXSocket < BasicSocket * @@ -1765,8 +1765,8 @@ rb_file_socket_p(VALUE obj, VALUE fname) if (rb_stat(fname, &st) < 0) return Qfalse; if (S_ISSOCK(st.st_mode)) return Qtrue; - #endif + return Qfalse; } @@ -5600,6 +5600,7 @@ rb_stat_init(VALUE obj, VALUE fname) if (STAT(StringValueCStr(fname), &st) == -1) { rb_sys_fail_path(fname); } + if (DATA_PTR(obj)) { xfree(DATA_PTR(obj)); DATA_PTR(obj) = NULL; diff --git a/include/ruby/win32.h b/include/ruby/win32.h index e03f345958..197eb8a802 100644 --- a/include/ruby/win32.h +++ b/include/ruby/win32.h @@ -19,11 +19,6 @@ RUBY_SYMBOL_EXPORT_BEGIN */ /* - * Definitions for NT port of Perl - */ - - -/* * Ok now we can include the normal include files. */ @@ -392,6 +387,7 @@ scalb(double a, long b) #endif #define S_IFLNK 0xa000 +#define S_IFSOCK 0xc000 /* * define this so we can do inplace editing diff --git a/test/fiber/test_enumerator.rb b/test/fiber/test_enumerator.rb index c635f474db..40f7d01725 100644 --- a/test/fiber/test_enumerator.rb +++ b/test/fiber/test_enumerator.rb @@ -10,12 +10,6 @@ class TestFiberEnumerator < Test::Unit::TestCase i, o = UNIXSocket.pair - unless i.nonblock? && o.nonblock? - i.close - o.close - omit "I/O is not non-blocking!" - end - message = String.new thread = Thread.new do diff --git a/test/fiber/test_io.rb b/test/fiber/test_io.rb index 821a169e44..de88745e57 100644 --- a/test/fiber/test_io.rb +++ b/test/fiber/test_io.rb @@ -6,14 +6,12 @@ class TestFiberIO < Test::Unit::TestCase MESSAGE = "Hello World" def test_read - omit "UNIXSocket is not defined!" unless defined?(UNIXSocket) + omit unless defined?(UNIXSocket) i, o = UNIXSocket.pair - - unless i.nonblock? && o.nonblock? - i.close - o.close - omit "I/O is not non-blocking!" + if RUBY_PLATFORM=~/mswin|mingw/ + i.nonblock = true + o.nonblock = true end message = nil @@ -46,6 +44,10 @@ class TestFiberIO < Test::Unit::TestCase 16.times.map do Thread.new do i, o = UNIXSocket.pair + if RUBY_PLATFORM=~/mswin|mingw/ + i.nonblock = true + o.nonblock = true + end scheduler = Scheduler.new Fiber.set_scheduler scheduler @@ -64,16 +66,11 @@ class TestFiberIO < Test::Unit::TestCase end def test_epipe_on_read - omit "UNIXSocket is not defined!" unless defined?(UNIXSocket) + omit unless defined?(UNIXSocket) + omit "nonblock=true isn't properly supported on Windows" if RUBY_PLATFORM=~/mswin|mingw/ i, o = UNIXSocket.pair - unless i.nonblock? && o.nonblock? - i.close - o.close - omit "I/O is not non-blocking!" - end - error = nil thread = Thread.new do diff --git a/test/fileutils/test_fileutils.rb b/test/fileutils/test_fileutils.rb index 05ba8d184a..2748bd247f 100644 --- a/test/fileutils/test_fileutils.rb +++ b/test/fileutils/test_fileutils.rb @@ -472,10 +472,14 @@ class TestFileUtils < Test::Unit::TestCase else def test_cp_r_socket pend "Skipping socket test on JRuby" if RUBY_ENGINE == 'jruby' + Dir.mkdir('tmp/cpr_src') UNIXServer.new('tmp/cpr_src/socket').close cp_r 'tmp/cpr_src', 'tmp/cpr_dest' assert_equal(true, File.socket?('tmp/cpr_dest/socket')) + rescue Errno::EINVAL => error + # On some platforms (windows) sockets cannot be copied by FileUtils. + omit error.message end if defined?(UNIXServer) end diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index 8cd020533b..d0472a0081 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -649,7 +649,7 @@ class TestFileExhaustive < Test::Unit::TestCase # ignore unsupporting filesystems rescue Errno::EPERM # Docker prohibits statx syscall by the default. - skip("statx(2) is prohibited by seccomp") + omit("statx(2) is prohibited by seccomp") end assert_raise(Errno::ENOENT) { File.birthtime(nofile) } end if File.respond_to?(:birthtime) diff --git a/test/ruby/test_io.rb b/test/ruby/test_io.rb index 6313e11179..0bf24960c6 100644 --- a/test/ruby/test_io.rb +++ b/test/ruby/test_io.rb @@ -900,6 +900,10 @@ class TestIO < Test::Unit::TestCase end if defined? UNIXSocket def test_copy_stream_socket4 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + with_bigsrc {|bigsrc, bigcontent| File.open(bigsrc) {|f| assert_equal(0, f.pos) @@ -916,9 +920,13 @@ class TestIO < Test::Unit::TestCase } } } - end if defined? UNIXSocket + end def test_copy_stream_socket5 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + with_bigsrc {|bigsrc, bigcontent| File.open(bigsrc) {|f| assert_equal(bigcontent[0,100], f.read(100)) @@ -936,9 +944,13 @@ class TestIO < Test::Unit::TestCase } } } - end if defined? UNIXSocket + end def test_copy_stream_socket6 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + mkcdtmpdir { megacontent = "abc" * 1234567 File.open("megasrc", "w") {|f| f << megacontent } @@ -959,9 +971,13 @@ class TestIO < Test::Unit::TestCase assert_equal(megacontent, result) } } - end if defined? UNIXSocket + end def test_copy_stream_socket7 + if RUBY_PLATFORM =~ /mingw|mswin/ + omit "pread(2) is not implemented." + end + GC.start mkcdtmpdir { megacontent = "abc" * 1234567 @@ -996,7 +1012,7 @@ class TestIO < Test::Unit::TestCase end } } - end if defined? UNIXSocket and IO.method_defined?("nonblock=") + end def test_copy_stream_strio src = StringIO.new("abcd") diff --git a/test/ruby/test_io_timeout.rb b/test/ruby/test_io_timeout.rb index ca4c0b833b..e017395980 100644 --- a/test/ruby/test_io_timeout.rb +++ b/test/ruby/test_io_timeout.rb @@ -9,12 +9,6 @@ class TestIOTimeout < Test::Unit::TestCase begin i, o = UNIXSocket.pair - unless i.nonblock? && o.nonblock? - i.close - o.close - omit "I/O is not non-blocking!" - end - yield i, o ensure i.close diff --git a/test/socket/test_nonblock.rb b/test/socket/test_nonblock.rb index d9d1e186b2..5a4688bac3 100644 --- a/test/socket/test_nonblock.rb +++ b/test/socket/test_nonblock.rb @@ -307,11 +307,13 @@ class TestSocketNonblock < Test::Unit::TestCase loop { s1.sendmsg_nonblock(buf) } end end - rescue NotImplementedError, Errno::ENOSYS, Errno::EPROTONOSUPPORT + rescue NotImplementedError, Errno::ENOSYS, Errno::EPROTONOSUPPORT, Errno::EPROTOTYPE omit "UNIXSocket.pair(:SEQPACKET) not implemented on this platform: #{$!}" end def test_sendmsg_nonblock_no_exception + omit "AF_UNIX + SEQPACKET is not supported on windows" if /mswin|mingw/ =~ RUBY_PLATFORM + buf = '*' * 4096 UNIXSocket.pair(:SEQPACKET) do |s1, s2| n = 0 diff --git a/test/socket/test_unix.rb b/test/socket/test_unix.rb index 8c74d0c939..b1dcc813e7 100644 --- a/test/socket/test_unix.rb +++ b/test/socket/test_unix.rb @@ -60,6 +60,8 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase assert_not_equal s1.fileno, r.fileno r.close end + rescue NotImplementedError => error + omit error.message end def test_fd_passing_n @@ -334,62 +336,70 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase end def test_noname_path - s1, s2 = UNIXSocket.pair - assert_equal("", s1.path) - assert_equal("", s2.path) - ensure - s1.close - s2.close + if /mswin|mingw/ =~ RUBY_PLATFORM + omit "unnamed pipe is emulated on windows" + end + + UNIXSocket.pair do |s1, s2| + assert_equal("", s1.path) + assert_equal("", s2.path) + end end def test_noname_addr - s1, s2 = UNIXSocket.pair - assert_equal(["AF_UNIX", ""], s1.addr) - assert_equal(["AF_UNIX", ""], s2.addr) - ensure - s1.close - s2.close + if /mswin|mingw/ =~ RUBY_PLATFORM + omit "unnamed pipe is emulated on windows" + end + + UNIXSocket.pair do |s1, s2| + assert_equal(["AF_UNIX", ""], s1.addr) + assert_equal(["AF_UNIX", ""], s2.addr) + end end def test_noname_peeraddr - s1, s2 = UNIXSocket.pair - assert_equal(["AF_UNIX", ""], s1.peeraddr) - assert_equal(["AF_UNIX", ""], s2.peeraddr) - ensure - s1.close - s2.close + if /mswin|mingw/ =~ RUBY_PLATFORM + omit "unnamed pipe is emulated on windows" + end + + UNIXSocket.pair do |s1, s2| + assert_equal(["AF_UNIX", ""], s1.peeraddr) + assert_equal(["AF_UNIX", ""], s2.peeraddr) + end end def test_noname_unpack_sockaddr_un - s1, s2 = UNIXSocket.pair - n = nil - assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != "" - assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != "" - assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getsockname) != "" - assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getpeername) != "" - assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getpeername) != "" - ensure - s1.close - s2.close + if /mswin|mingw/ =~ RUBY_PLATFORM + omit "unnamed pipe is emulated on windows" + end + + UNIXSocket.pair do |s1, s2| + n = nil + assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != "" + assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != "" + assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getsockname) != "" + assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getpeername) != "" + assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getpeername) != "" + end end def test_noname_recvfrom - s1, s2 = UNIXSocket.pair - s2.write("a") - assert_equal(["a", ["AF_UNIX", ""]], s1.recvfrom(10)) - ensure - s1.close - s2.close + if /mswin|mingw/ =~ RUBY_PLATFORM + omit "unnamed pipe is emulated on windows" + end + + UNIXSocket.pair do |s1, s2| + s2.write("a") + assert_equal(["a", ["AF_UNIX", ""]], s1.recvfrom(10)) + end end def test_noname_recv_nonblock - s1, s2 = UNIXSocket.pair - s2.write("a") - IO.select [s1] - assert_equal("a", s1.recv_nonblock(10)) - ensure - s1.close - s2.close + UNIXSocket.pair do |s1, s2| + s2.write("a") + IO.select [s1] + assert_equal("a", s1.recv_nonblock(10)) + end end def test_too_long_path @@ -429,12 +439,18 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase rv = s1.recv(100, 0, buf) assert_equal buf.object_id, rv.object_id assert_equal "BBBBBB", rv + rescue Errno::EPROTOTYPE => error + omit error.message ensure s1.close if s1 s2.close if s2 end def test_dgram_pair_sendrecvmsg_errno_set + if /mswin|mingw/ =~ RUBY_PLATFORM + omit("AF_UNIX + SOCK_DGRAM is not supported on windows") + end + s1, s2 = to_close = UNIXSocket.pair(Socket::SOCK_DGRAM) pipe = IO.pipe to_close.concat(pipe) @@ -457,9 +473,17 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase end def test_epipe # [ruby-dev:34619] + # This is a good example of why reporting the exact `errno` is a terrible + # idea for platform abstractions. + if RUBY_PLATFORM =~ /mswin|mingw/ + error = Errno::ESHUTDOWN + else + error = Errno::EPIPE + end + UNIXSocket.pair {|s1, s2| s1.shutdown(Socket::SHUT_WR) - assert_raise(Errno::EPIPE) { s1.write "a" } + assert_raise(error) { s1.write "a" } assert_equal(nil, s2.read(1)) s2.write "a" assert_equal("a", s1.read(1)) @@ -493,6 +517,45 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase } end + if /mingw|mswin/ =~ RUBY_PLATFORM + + def test_unix_socket_with_encoding + Dir.mktmpdir do |tmpdir| + path = "#{tmpdir}/sockäöü".encode("cp850") + UNIXServer.open(path) do |serv| + assert File.socket?(path) + assert File.stat(path).socket? + assert File.lstat(path).socket? + assert_equal path.encode("utf-8"), serv.path + UNIXSocket.open(path) do |s1| + s2 = serv.accept + s2.close + end + end + end + end + + def test_windows_unix_socket_pair_with_umlaut + otmp = ENV['TMP'] + ENV['TMP'] = File.join(Dir.tmpdir, "äöü€") + FileUtils.mkdir_p ENV['TMP'] + + s1, s2 = UNIXSocket.pair + assert !s1.path.empty? + assert !File.exist?(s1.path) + ensure + FileUtils.rm_rf ENV['TMP'] + ENV['TMP'] = otmp + end + + def test_windows_unix_socket_pair_paths + s1, s2 = UNIXSocket.pair + assert !s1.path.empty? + assert s2.path.empty? + assert !File.exist?(s1.path) + end + end + def test_initialize Dir.mktmpdir {|d| Socket.open(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) {|s| diff --git a/win32/Makefile.sub b/win32/Makefile.sub index e84f978bb7..4b8904c536 100644 --- a/win32/Makefile.sub +++ b/win32/Makefile.sub @@ -629,6 +629,9 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub #define HAVE_STDDEF_H 1 #define HAVE_STRING_H 1 #define HAVE_MEMORY_H 1 +!if $(MSC_VER) >= 1920 +#define HAVE_AFUNIX_H 1 +!endif !if $(MSC_VER) >= 1400 #define HAVE_LONG_LONG 1 !else diff --git a/win32/file.h b/win32/file.h index ef701487dd..4f1f36a75c 100644 --- a/win32/file.h +++ b/win32/file.h @@ -1,6 +1,10 @@ #ifndef RUBY_WIN32_FILE_H #define RUBY_WIN32_FILE_H +#ifndef IO_REPARSE_TAG_AF_UNIX +# define IO_REPARSE_TAG_AF_UNIX 0x80000023 +#endif + enum { MINIMUM_REPARSE_BUFFER_PATH_LEN = 100 }; diff --git a/win32/win32.c b/win32/win32.c index 3ddfe9bfdf..fee31677f0 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -49,6 +49,9 @@ #ifdef __MINGW32__ #include <mswsock.h> #endif +#ifdef HAVE_AFUNIX_H +# include <afunix.h> +#endif #include "ruby/win32.h" #include "ruby/vm.h" #include "win32/dir.h" @@ -4018,15 +4021,93 @@ rb_w32_getservbyport(int port, const char *proto) return r; } +#ifdef HAVE_AFUNIX_H + +/* License: Ruby's */ +static size_t +socketpair_unix_path(struct sockaddr_un *sock_un) +{ + SOCKET listener; + WCHAR wpath[sizeof(sock_un->sun_path)/sizeof(*sock_un->sun_path)] = L""; + + /* AF_UNIX/SOCK_STREAM became available in Windows 10 + * See https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows + */ + listener = socket(AF_UNIX, SOCK_STREAM, 0); + if (listener == INVALID_SOCKET) + return 0; + + memset(sock_un, 0, sizeof(*sock_un)); + sock_un->sun_family = AF_UNIX; + + /* Abstract sockets (filesystem-independent) don't work, contrary to + * the claims of the aforementioned blog post: + * https://github.com/microsoft/WSL/issues/4240#issuecomment-549663217 + * + * So we must use a named path, and that comes with all the attendant + * problems of permissions and collisions. Trying various temporary + * directories and putting high-res time and PID in the filename. + */ + for (int try = 0; ; try++) { + LARGE_INTEGER ticks; + size_t path_len = 0; + const size_t maxpath = sizeof(sock_un->sun_path)/sizeof(*sock_un->sun_path); + + switch (try) { + case 0: + /* user temp dir from TMP or TEMP env var, it ends with a backslash */ + path_len = GetTempPathW(maxpath, wpath); + break; + case 1: + wcsncpy(wpath, L"C:/Temp/", maxpath); + path_len = lstrlenW(wpath); + break; + case 2: + /* Current directory */ + path_len = 0; + break; + case 3: + closesocket(listener); + return 0; + } + + /* Windows UNIXSocket implementation expects UTF-8 instead of UTF16 */ + path_len = WideCharToMultiByte(CP_UTF8, 0, wpath, path_len, sock_un->sun_path, maxpath, NULL, NULL); + QueryPerformanceCounter(&ticks); + path_len += snprintf(sock_un->sun_path + path_len, + maxpath - path_len, + "%lld-%ld.($)", + ticks.QuadPart, + GetCurrentProcessId()); + + /* Convert to UTF16 for DeleteFileW */ + MultiByteToWideChar(CP_UTF8, 0, sock_un->sun_path, -1, wpath, sizeof(wpath)/sizeof(*wpath)); + + if (bind(listener, (struct sockaddr *)sock_un, sizeof(*sock_un)) != SOCKET_ERROR) + break; + } + closesocket(listener); + DeleteFileW(wpath); + return sizeof(*sock_un); +} +#endif + /* License: Ruby's */ static int socketpair_internal(int af, int type, int protocol, SOCKET *sv) { SOCKET svr = INVALID_SOCKET, r = INVALID_SOCKET, w = INVALID_SOCKET; struct sockaddr_in sock_in4; + #ifdef INET6 struct sockaddr_in6 sock_in6; #endif + +#ifdef HAVE_AFUNIX_H + struct sockaddr_un sock_un = {0, {0}}; + WCHAR wpath[sizeof(sock_un.sun_path)/sizeof(*sock_un.sun_path)] = L""; +#endif + struct sockaddr *addr; int ret = -1; int len; @@ -4051,6 +4132,15 @@ socketpair_internal(int af, int type, int protocol, SOCKET *sv) len = sizeof(sock_in6); break; #endif +#ifdef HAVE_AFUNIX_H + case AF_UNIX: + addr = (struct sockaddr *)&sock_un; + len = socketpair_unix_path(&sock_un); + MultiByteToWideChar(CP_UTF8, 0, sock_un.sun_path, -1, wpath, sizeof(wpath)/sizeof(*wpath)); + if (len) + break; + /* fall through */ +#endif default: errno = EAFNOSUPPORT; return -1; @@ -4101,6 +4191,10 @@ socketpair_internal(int af, int type, int protocol, SOCKET *sv) } if (svr != INVALID_SOCKET) closesocket(svr); +#ifdef HAVE_AFUNIX_H + if (sock_un.sun_family == AF_UNIX) + DeleteFileW(wpath); +#endif } return ret; @@ -5632,10 +5726,8 @@ fileattr_to_unixmode(DWORD attr, const WCHAR *path, unsigned mode) /* format is already set */ } else if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { - if (rb_w32_reparse_symlink_p(path)) - mode |= S_IFLNK | S_IEXEC; - else - mode |= S_IFDIR | S_IEXEC; + /* Only used by stat_by_find in the case the file can not be opened. + * In this case we can't get more details. */ } else if (attr & FILE_ATTRIBUTE_DIRECTORY) { mode |= S_IFDIR | S_IEXEC; @@ -5710,14 +5802,6 @@ stat_by_find(const WCHAR *path, struct stati128 *st) { HANDLE h; WIN32_FIND_DATAW wfd; - /* GetFileAttributesEx failed; check why. */ - int e = GetLastError(); - - if ((e == ERROR_FILE_NOT_FOUND) || (e == ERROR_INVALID_NAME) - || (e == ERROR_PATH_NOT_FOUND || (e == ERROR_BAD_NETPATH))) { - errno = map_errno(e); - return -1; - } /* Fall back to FindFirstFile for ERROR_SHARING_VIOLATION */ h = FindFirstFileW(path, &wfd); @@ -5753,9 +5837,24 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) DWORD flags = lstat ? FILE_FLAG_OPEN_REPARSE_POINT : 0; HANDLE f; WCHAR finalname[PATH_MAX]; + int open_error; memset(st, 0, sizeof(*st)); f = open_special(path, 0, flags); + open_error = GetLastError(); + if (f == INVALID_HANDLE_VALUE && !lstat) { + /* Support stat (not only lstat) of UNIXSocket */ + FILE_ATTRIBUTE_TAG_INFO attr_info; + DWORD e; + + f = open_special(path, 0, FILE_FLAG_OPEN_REPARSE_POINT); + e = GetFileInformationByHandleEx( f, FileAttributeTagInfo, + &attr_info, sizeof(attr_info)); + if (!e || attr_info.ReparseTag != IO_REPARSE_TAG_AF_UNIX) { + CloseHandle(f); + f = INVALID_HANDLE_VALUE; + } + } if (f != INVALID_HANDLE_VALUE) { DWORD attr = stati128_handle(f, st); const DWORD len = get_final_path(f, finalname, numberof(finalname), 0); @@ -5767,15 +5866,26 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) case FILE_TYPE_PIPE: mode = S_IFIFO; break; + default: + if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { + FILE_ATTRIBUTE_TAG_INFO attr_info; + DWORD e; + + e = GetFileInformationByHandleEx( f, FileAttributeTagInfo, + &attr_info, sizeof(attr_info)); + if (e && attr_info.ReparseTag == IO_REPARSE_TAG_AF_UNIX) { + st->st_size = 0; + mode |= S_IFSOCK; + } else if (rb_w32_reparse_symlink_p(path)) { + /* TODO: size in which encoding? */ + st->st_size = 0; + mode |= S_IFLNK | S_IEXEC; + } else { + mode |= S_IFDIR | S_IEXEC; + } + } } CloseHandle(f); - if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { - /* TODO: size in which encoding? */ - if (rb_w32_reparse_symlink_p(path)) - st->st_size = 0; - else - attr &= ~FILE_ATTRIBUTE_REPARSE_POINT; - } if (attr & FILE_ATTRIBUTE_DIRECTORY) { if (check_valid_dir(path)) return -1; } @@ -5788,6 +5898,12 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat) } } else { + if ((open_error == ERROR_FILE_NOT_FOUND) || (open_error == ERROR_INVALID_NAME) + || (open_error == ERROR_PATH_NOT_FOUND || (open_error == ERROR_BAD_NETPATH))) { + errno = map_errno(open_error); + return -1; + } + if (stat_by_find(path, st)) return -1; } |