summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS.md9
-rw-r--r--configure.ac5
-rw-r--r--ext/socket/extconf.rb1
-rw-r--r--ext/socket/init.c2
-rw-r--r--ext/socket/lib/socket.rb2
-rw-r--r--ext/socket/option.c10
-rw-r--r--ext/socket/raddrinfo.c20
-rw-r--r--ext/socket/rubysocket.h12
-rw-r--r--ext/socket/socket.c6
-rw-r--r--ext/socket/unixserver.c4
-rw-r--r--ext/socket/unixsocket.c8
-rw-r--r--file.c3
-rw-r--r--include/ruby/win32.h6
-rw-r--r--test/fiber/test_enumerator.rb6
-rw-r--r--test/fiber/test_io.rb23
-rw-r--r--test/fileutils/test_fileutils.rb4
-rw-r--r--test/ruby/test_file_exhaustive.rb2
-rw-r--r--test/ruby/test_io.rb24
-rw-r--r--test/ruby/test_io_timeout.rb6
-rw-r--r--test/socket/test_nonblock.rb4
-rw-r--r--test/socket/test_unix.rb147
-rw-r--r--win32/Makefile.sub3
-rw-r--r--win32/file.h4
-rw-r--r--win32/win32.c154
24 files changed, 340 insertions, 125 deletions
diff --git a/NEWS.md b/NEWS.md
index 7cd1533512..a4b4ae6bd8 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -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
*
diff --git a/file.c b/file.c
index 3e772bd099..10eb1081e3 100644
--- a/file.c
+++ b/file.c
@@ -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;
}