Add support for sockaddr_un on Windows. (#6513)

* Windows: Fix warning about undefined if_indextoname()

* Windows: Fix UNIXSocket on MINGW and make .pair more reliable

* Windows: Use nonblock=true for read tests with scheduler

* Windows: Move socket detection from File.socket? to File.stat

Add S_IFSOCK to Windows and interpret reparse points accordingly.
Enable tests that work now.

* Windows: Use wide-char functions to UNIXSocket

This fixes behaviour with non-ASCII characters.
It also fixes deletion of temporary UNIXSocket.pair files.

* Windows: Add UNIXSocket tests for specifics of Windows impl.

* Windows: fix VC build due to missing _snwprintf

Avoid usage of _snwprintf, since it fails linking ruby.dll like so:

  linking shared-library x64-vcruntime140-ruby320.dll
  x64-vcruntime140-ruby320.def : error LNK2001: unresolved external symbol snwprintf
  x64-vcruntime140-ruby320.def : error LNK2001: unresolved external symbol vsnwprintf_l

whereas linking miniruby.exe succeeds.

This patch uses snprintf on the UTF-8 string instead.

Also remove branch GetWindowsDirectoryW, since it doesn't work.

* Windows: Fix dangling symlink test failures

Co-authored-by: Lars Kanis <kanis@comcard.de>
This commit is contained in:
Samuel Williams 2022-11-17 14:50:25 -08:00 committed by GitHub
parent 4e4b29b1a9
commit ea8a7287e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
Notes: git 2022-11-17 22:50:45 +00:00
Merged-By: ioquatix <samuel@codeotaku.com>
24 changed files with 340 additions and 125 deletions

View File

@ -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. Note: We're only listing outstanding class updates.
* Fiber::Scheduler * 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 * IO
* Introduce `IO#timeout=` and `IO#timeout` which can cause * 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) 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
* `Class#attached_object`, which returns the object for which * `Class#attached_object`, which returns the object for which
the receiver is the singleton class. Raises `TypeError` if the 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 #19026]: https://bugs.ruby-lang.org/issues/19026
[Feature #19060]: https://bugs.ruby-lang.org/issues/19060 [Feature #19060]: https://bugs.ruby-lang.org/issues/19060
[Bug #19100]: https://bugs.ruby-lang.org/issues/19100 [Bug #19100]: https://bugs.ruby-lang.org/issues/19100
[Feature #19135]: https://bugs.ruby-lang.org/issues/19135

View File

@ -1283,6 +1283,11 @@ dnl AC_HEADER_STDC has been checked in AC_USE_SYSTEM_EXTENSIONS
AC_HEADER_STDBOOL AC_HEADER_STDBOOL
AC_HEADER_SYS_WAIT AC_HEADER_SYS_WAIT
AC_CHECK_HEADERS([afunix.h], [], [],
[#ifdef _WIN32
# include <winsock2.h>
#endif
])
AC_CHECK_HEADERS(atomic.h) AC_CHECK_HEADERS(atomic.h)
AC_CHECK_HEADERS(copyfile.h) AC_CHECK_HEADERS(copyfile.h)
AC_CHECK_HEADERS(direct.h) AC_CHECK_HEADERS(direct.h)

View File

@ -316,6 +316,7 @@ end
netpacket/packet.h netpacket/packet.h
net/ethernet.h net/ethernet.h
sys/un.h sys/un.h
afunix.h
ifaddrs.h ifaddrs.h
sys/ioctl.h sys/ioctl.h
sys/sockio.h sys/sockio.h

View File

@ -209,7 +209,7 @@ rsock_s_recvfrom(VALUE socket, int argc, VALUE *argv, enum sock_recv_type from)
else else
return rb_assoc_new(str, Qnil); return rb_assoc_new(str, Qnil);
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
case RECV_UNIX: case RECV_UNIX:
return rb_assoc_new(str, rsock_unixaddr(&arg.buf.un, arg.alen)); return rb_assoc_new(str, rsock_unixaddr(&arg.buf.un, arg.alen));
#endif #endif

View File

@ -197,7 +197,7 @@ class Addrinfo
sock = Socket.new(self.pfamily, self.socktype, self.protocol) sock = Socket.new(self.pfamily, self.socktype, self.protocol)
begin begin
sock.ipv6only! if self.ipv6? 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.bind(self)
sock.listen(backlog) sock.listen(backlog)
rescue Exception rescue Exception

View File

@ -670,10 +670,10 @@ rb_if_indextoname(const char *succ_prefix, const char *fail_prefix, unsigned int
{ {
#if defined(HAVE_IF_INDEXTONAME) #if defined(HAVE_IF_INDEXTONAME)
char ifbuf[IFNAMSIZ]; char ifbuf[IFNAMSIZ];
if (if_indextoname(ifindex, ifbuf) == NULL) if (if_indextoname(ifindex, ifbuf))
return snprintf(buf, len, "%s%u", fail_prefix, ifindex);
else
return snprintf(buf, len, "%s%s", succ_prefix, ifbuf); return snprintf(buf, len, "%s%s", succ_prefix, ifbuf);
else
return snprintf(buf, len, "%s%u", fail_prefix, ifindex);
#else #else
# ifndef IFNAMSIZ # ifndef IFNAMSIZ
# define IFNAMSIZ (sizeof(unsigned int)*3+1) # define IFNAMSIZ (sizeof(unsigned int)*3+1)
@ -1229,7 +1229,7 @@ sockopt_inspect(VALUE self)
else else
rb_str_catf(ret, " optname:%d", optname); rb_str_catf(ret, " optname:%d", optname);
} }
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
else if (family == AF_UNIX) { else if (family == AF_UNIX) {
rb_str_catf(ret, " level:%d", level); rb_str_catf(ret, " level:%d", level);
@ -1393,7 +1393,7 @@ sockopt_inspect(VALUE self)
} }
break; break;
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
case AF_UNIX: case AF_UNIX:
switch (level) { switch (level) {
case 0: case 0:

View File

@ -644,7 +644,7 @@ rsock_ipaddr(struct sockaddr *sockaddr, socklen_t sockaddrlen, int norevlookup)
return ary; return ary;
} }
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
static long static long
unixsocket_len(const struct sockaddr_un *su, socklen_t socklen) 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 static void
init_unix_addrinfo(rb_addrinfo_t *rai, VALUE path, int socktype) init_unix_addrinfo(rb_addrinfo_t *rai, VALUE path, int socktype)
{ {
@ -1146,7 +1146,7 @@ addrinfo_initialize(int argc, VALUE *argv, VALUE self)
break; break;
} }
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
case AF_UNIX: /* ["AF_UNIX", "/tmp/sock"] */ case AF_UNIX: /* ["AF_UNIX", "/tmp/sock"] */
{ {
VALUE path = rb_ary_entry(sockaddr_ary, 1); 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 #endif
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
case AF_UNIX: case AF_UNIX:
{ {
struct sockaddr_un *addr = &sockaddr->un; struct sockaddr_un *addr = &sockaddr->un;
@ -1622,7 +1622,7 @@ addrinfo_mdump(VALUE self)
afamily = rb_id2str(id); afamily = rb_id2str(id);
switch(afamily_int) { switch(afamily_int) {
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
case AF_UNIX: case AF_UNIX:
{ {
sockaddr = rb_str_new(rai->addr.un.sun_path, rai_unixsocket_len(rai)); 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); v = rb_ary_entry(ary, 1);
switch(afamily) { switch(afamily) {
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
case AF_UNIX: case AF_UNIX:
{ {
struct sockaddr_un uaddr; struct sockaddr_un uaddr;
@ -2343,7 +2343,7 @@ addrinfo_ipv6_to_ipv4(VALUE self)
#endif #endif
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
/* /*
* call-seq: * call-seq:
* addrinfo.unix_path => path * 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)); INT2NUM(PF_UNSPEC), INT2NUM(SOCK_DGRAM), INT2NUM(IPPROTO_UDP), INT2FIX(0));
} }
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
/* /*
* call-seq: * 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, "ip", addrinfo_s_ip, 1);
rb_define_singleton_method(rb_cAddrinfo, "tcp", addrinfo_s_tcp, 2); rb_define_singleton_method(rb_cAddrinfo, "tcp", addrinfo_s_tcp, 2);
rb_define_singleton_method(rb_cAddrinfo, "udp", addrinfo_s_udp, 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); rb_define_singleton_method(rb_cAddrinfo, "unix", addrinfo_s_unix, -1);
#endif #endif
@ -2670,7 +2670,7 @@ rsock_init_addrinfo(void)
rb_define_method(rb_cAddrinfo, "ipv6_to_ipv4", addrinfo_ipv6_to_ipv4, 0); rb_define_method(rb_cAddrinfo, "ipv6_to_ipv4", addrinfo_ipv6_to_ipv4, 0);
#endif #endif
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
rb_define_method(rb_cAddrinfo, "unix_path", addrinfo_unix_path, 0); rb_define_method(rb_cAddrinfo, "unix_path", addrinfo_unix_path, 0);
#endif #endif

View File

@ -33,6 +33,9 @@
#endif #endif
#ifdef _WIN32 #ifdef _WIN32
# include <winsock2.h>
# include <ws2tcpip.h>
# include <iphlpapi.h>
# if defined(_MSC_VER) # if defined(_MSC_VER)
# undef HAVE_TYPE_STRUCT_SOCKADDR_DL # undef HAVE_TYPE_STRUCT_SOCKADDR_DL
# endif # endif
@ -69,6 +72,11 @@
# include <sys/un.h> # include <sys/un.h>
#endif #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) #if defined(HAVE_FCNTL)
# ifdef HAVE_SYS_SELECT_H # ifdef HAVE_SYS_SELECT_H
# include <sys/select.h> # include <sys/select.h>
@ -268,7 +276,7 @@ extern VALUE rb_cIPSocket;
extern VALUE rb_cTCPSocket; extern VALUE rb_cTCPSocket;
extern VALUE rb_cTCPServer; extern VALUE rb_cTCPServer;
extern VALUE rb_cUDPSocket; extern VALUE rb_cUDPSocket;
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
extern VALUE rb_cUNIXSocket; extern VALUE rb_cUNIXSocket;
extern VALUE rb_cUNIXServer; extern VALUE rb_cUNIXServer;
#endif #endif
@ -336,7 +344,7 @@ VALUE rsock_sockaddr_obj(struct sockaddr *addr, socklen_t len);
int rsock_revlookup_flag(VALUE revlookup, int *norevlookup); 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_unixpath_str(struct sockaddr_un *sockaddr, socklen_t len);
VALUE rsock_unixaddr(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); socklen_t rsock_unix_sockaddr_len(VALUE path);

View File

@ -1383,7 +1383,7 @@ sock_s_unpack_sockaddr_in(VALUE self, VALUE addr)
return rb_assoc_new(INT2NUM(ntohs(sockaddr->sin_port)), host); return rb_assoc_new(INT2NUM(ntohs(sockaddr->sin_port)), host);
} }
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
/* /*
* call-seq: * call-seq:
@ -1471,7 +1471,7 @@ sockaddr_len(struct sockaddr *addr)
return (socklen_t)sizeof(struct sockaddr_in6); return (socklen_t)sizeof(struct sockaddr_in6);
#endif #endif
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
case AF_UNIX: case AF_UNIX:
return (socklen_t)sizeof(struct sockaddr_un); return (socklen_t)sizeof(struct sockaddr_un);
#endif #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, "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, "pack_sockaddr_in", sock_s_pack_sockaddr_in, 2);
rb_define_singleton_method(rb_cSocket, "unpack_sockaddr_in", sock_s_unpack_sockaddr_in, 1); 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, "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, "pack_sockaddr_un", sock_s_pack_sockaddr_un, 1);
rb_define_singleton_method(rb_cSocket, "unpack_sockaddr_un", sock_s_unpack_sockaddr_un, 1); rb_define_singleton_method(rb_cSocket, "unpack_sockaddr_un", sock_s_unpack_sockaddr_un, 1);

View File

@ -10,7 +10,7 @@
#include "rubysocket.h" #include "rubysocket.h"
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
/* /*
* call-seq: * call-seq:
* UNIXServer.new(path) => unixserver * UNIXServer.new(path) => unixserver
@ -101,7 +101,7 @@ unix_sysaccept(VALUE server)
void void
rsock_init_unixserver(void) rsock_init_unixserver(void)
{ {
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
/* /*
* Document-class: UNIXServer < UNIXSocket * Document-class: UNIXServer < UNIXSocket
* *

View File

@ -10,7 +10,7 @@
#include "rubysocket.h" #include "rubysocket.h"
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
struct unixsock_arg { struct unixsock_arg {
struct sockaddr_un *sockaddr; struct sockaddr_un *sockaddr;
socklen_t sockaddrlen; socklen_t sockaddrlen;
@ -42,6 +42,10 @@ unixsock_path_value(VALUE path)
return name; /* ignore encoding */ return name; /* ignore encoding */
} }
} }
#endif
#ifdef _WIN32
/* UNIXSocket requires UTF-8 per spec. */
path = rb_str_export_to_enc(path, rb_utf8_encoding());
#endif #endif
return rb_get_path(path); return rb_get_path(path);
} }
@ -571,7 +575,7 @@ unix_s_socketpair(int argc, VALUE *argv, VALUE klass)
void void
rsock_init_unixsocket(void) rsock_init_unixsocket(void)
{ {
#ifdef HAVE_SYS_UN_H #ifdef HAVE_TYPE_STRUCT_SOCKADDR_UN
/* /*
* Document-class: UNIXSocket < BasicSocket * Document-class: UNIXSocket < BasicSocket
* *

3
file.c
View File

@ -1765,8 +1765,8 @@ rb_file_socket_p(VALUE obj, VALUE fname)
if (rb_stat(fname, &st) < 0) return Qfalse; if (rb_stat(fname, &st) < 0) return Qfalse;
if (S_ISSOCK(st.st_mode)) return Qtrue; if (S_ISSOCK(st.st_mode)) return Qtrue;
#endif #endif
return Qfalse; return Qfalse;
} }
@ -5600,6 +5600,7 @@ rb_stat_init(VALUE obj, VALUE fname)
if (STAT(StringValueCStr(fname), &st) == -1) { if (STAT(StringValueCStr(fname), &st) == -1) {
rb_sys_fail_path(fname); rb_sys_fail_path(fname);
} }
if (DATA_PTR(obj)) { if (DATA_PTR(obj)) {
xfree(DATA_PTR(obj)); xfree(DATA_PTR(obj));
DATA_PTR(obj) = NULL; DATA_PTR(obj) = NULL;

View File

@ -18,11 +18,6 @@ RUBY_SYMBOL_EXPORT_BEGIN
* *
*/ */
/*
* Definitions for NT port of Perl
*/
/* /*
* Ok now we can include the normal include files. * Ok now we can include the normal include files.
*/ */
@ -392,6 +387,7 @@ scalb(double a, long b)
#endif #endif
#define S_IFLNK 0xa000 #define S_IFLNK 0xa000
#define S_IFSOCK 0xc000
/* /*
* define this so we can do inplace editing * define this so we can do inplace editing

View File

@ -10,12 +10,6 @@ class TestFiberEnumerator < Test::Unit::TestCase
i, o = UNIXSocket.pair i, o = UNIXSocket.pair
unless i.nonblock? && o.nonblock?
i.close
o.close
omit "I/O is not non-blocking!"
end
message = String.new message = String.new
thread = Thread.new do thread = Thread.new do

View File

@ -6,14 +6,12 @@ class TestFiberIO < Test::Unit::TestCase
MESSAGE = "Hello World" MESSAGE = "Hello World"
def test_read def test_read
omit "UNIXSocket is not defined!" unless defined?(UNIXSocket) omit unless defined?(UNIXSocket)
i, o = UNIXSocket.pair i, o = UNIXSocket.pair
if RUBY_PLATFORM=~/mswin|mingw/
unless i.nonblock? && o.nonblock? i.nonblock = true
i.close o.nonblock = true
o.close
omit "I/O is not non-blocking!"
end end
message = nil message = nil
@ -46,6 +44,10 @@ class TestFiberIO < Test::Unit::TestCase
16.times.map do 16.times.map do
Thread.new do Thread.new do
i, o = UNIXSocket.pair i, o = UNIXSocket.pair
if RUBY_PLATFORM=~/mswin|mingw/
i.nonblock = true
o.nonblock = true
end
scheduler = Scheduler.new scheduler = Scheduler.new
Fiber.set_scheduler scheduler Fiber.set_scheduler scheduler
@ -64,16 +66,11 @@ class TestFiberIO < Test::Unit::TestCase
end end
def test_epipe_on_read 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 i, o = UNIXSocket.pair
unless i.nonblock? && o.nonblock?
i.close
o.close
omit "I/O is not non-blocking!"
end
error = nil error = nil
thread = Thread.new do thread = Thread.new do

View File

@ -472,10 +472,14 @@ class TestFileUtils < Test::Unit::TestCase
else else
def test_cp_r_socket def test_cp_r_socket
pend "Skipping socket test on JRuby" if RUBY_ENGINE == 'jruby' pend "Skipping socket test on JRuby" if RUBY_ENGINE == 'jruby'
Dir.mkdir('tmp/cpr_src') Dir.mkdir('tmp/cpr_src')
UNIXServer.new('tmp/cpr_src/socket').close UNIXServer.new('tmp/cpr_src/socket').close
cp_r 'tmp/cpr_src', 'tmp/cpr_dest' cp_r 'tmp/cpr_src', 'tmp/cpr_dest'
assert_equal(true, File.socket?('tmp/cpr_dest/socket')) 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 if defined?(UNIXServer)
end end

View File

@ -649,7 +649,7 @@ class TestFileExhaustive < Test::Unit::TestCase
# ignore unsupporting filesystems # ignore unsupporting filesystems
rescue Errno::EPERM rescue Errno::EPERM
# Docker prohibits statx syscall by the default. # Docker prohibits statx syscall by the default.
skip("statx(2) is prohibited by seccomp") omit("statx(2) is prohibited by seccomp")
end end
assert_raise(Errno::ENOENT) { File.birthtime(nofile) } assert_raise(Errno::ENOENT) { File.birthtime(nofile) }
end if File.respond_to?(:birthtime) end if File.respond_to?(:birthtime)

View File

@ -900,6 +900,10 @@ class TestIO < Test::Unit::TestCase
end if defined? UNIXSocket end if defined? UNIXSocket
def test_copy_stream_socket4 def test_copy_stream_socket4
if RUBY_PLATFORM =~ /mingw|mswin/
omit "pread(2) is not implemented."
end
with_bigsrc {|bigsrc, bigcontent| with_bigsrc {|bigsrc, bigcontent|
File.open(bigsrc) {|f| File.open(bigsrc) {|f|
assert_equal(0, f.pos) assert_equal(0, f.pos)
@ -916,9 +920,13 @@ class TestIO < Test::Unit::TestCase
} }
} }
} }
end if defined? UNIXSocket end
def test_copy_stream_socket5 def test_copy_stream_socket5
if RUBY_PLATFORM =~ /mingw|mswin/
omit "pread(2) is not implemented."
end
with_bigsrc {|bigsrc, bigcontent| with_bigsrc {|bigsrc, bigcontent|
File.open(bigsrc) {|f| File.open(bigsrc) {|f|
assert_equal(bigcontent[0,100], f.read(100)) 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 def test_copy_stream_socket6
if RUBY_PLATFORM =~ /mingw|mswin/
omit "pread(2) is not implemented."
end
mkcdtmpdir { mkcdtmpdir {
megacontent = "abc" * 1234567 megacontent = "abc" * 1234567
File.open("megasrc", "w") {|f| f << megacontent } File.open("megasrc", "w") {|f| f << megacontent }
@ -959,9 +971,13 @@ class TestIO < Test::Unit::TestCase
assert_equal(megacontent, result) assert_equal(megacontent, result)
} }
} }
end if defined? UNIXSocket end
def test_copy_stream_socket7 def test_copy_stream_socket7
if RUBY_PLATFORM =~ /mingw|mswin/
omit "pread(2) is not implemented."
end
GC.start GC.start
mkcdtmpdir { mkcdtmpdir {
megacontent = "abc" * 1234567 megacontent = "abc" * 1234567
@ -996,7 +1012,7 @@ class TestIO < Test::Unit::TestCase
end end
} }
} }
end if defined? UNIXSocket and IO.method_defined?("nonblock=") end
def test_copy_stream_strio def test_copy_stream_strio
src = StringIO.new("abcd") src = StringIO.new("abcd")

View File

@ -9,12 +9,6 @@ class TestIOTimeout < Test::Unit::TestCase
begin begin
i, o = UNIXSocket.pair i, o = UNIXSocket.pair
unless i.nonblock? && o.nonblock?
i.close
o.close
omit "I/O is not non-blocking!"
end
yield i, o yield i, o
ensure ensure
i.close i.close

View File

@ -307,11 +307,13 @@ class TestSocketNonblock < Test::Unit::TestCase
loop { s1.sendmsg_nonblock(buf) } loop { s1.sendmsg_nonblock(buf) }
end end
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: #{$!}" omit "UNIXSocket.pair(:SEQPACKET) not implemented on this platform: #{$!}"
end end
def test_sendmsg_nonblock_no_exception def test_sendmsg_nonblock_no_exception
omit "AF_UNIX + SEQPACKET is not supported on windows" if /mswin|mingw/ =~ RUBY_PLATFORM
buf = '*' * 4096 buf = '*' * 4096
UNIXSocket.pair(:SEQPACKET) do |s1, s2| UNIXSocket.pair(:SEQPACKET) do |s1, s2|
n = 0 n = 0

View File

@ -60,6 +60,8 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
assert_not_equal s1.fileno, r.fileno assert_not_equal s1.fileno, r.fileno
r.close r.close
end end
rescue NotImplementedError => error
omit error.message
end end
def test_fd_passing_n def test_fd_passing_n
@ -334,62 +336,70 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
end end
def test_noname_path def test_noname_path
s1, s2 = UNIXSocket.pair if /mswin|mingw/ =~ RUBY_PLATFORM
assert_equal("", s1.path) omit "unnamed pipe is emulated on windows"
assert_equal("", s2.path) end
ensure
s1.close UNIXSocket.pair do |s1, s2|
s2.close assert_equal("", s1.path)
assert_equal("", s2.path)
end
end end
def test_noname_addr def test_noname_addr
s1, s2 = UNIXSocket.pair if /mswin|mingw/ =~ RUBY_PLATFORM
assert_equal(["AF_UNIX", ""], s1.addr) omit "unnamed pipe is emulated on windows"
assert_equal(["AF_UNIX", ""], s2.addr) end
ensure
s1.close UNIXSocket.pair do |s1, s2|
s2.close assert_equal(["AF_UNIX", ""], s1.addr)
assert_equal(["AF_UNIX", ""], s2.addr)
end
end end
def test_noname_peeraddr def test_noname_peeraddr
s1, s2 = UNIXSocket.pair if /mswin|mingw/ =~ RUBY_PLATFORM
assert_equal(["AF_UNIX", ""], s1.peeraddr) omit "unnamed pipe is emulated on windows"
assert_equal(["AF_UNIX", ""], s2.peeraddr) end
ensure
s1.close UNIXSocket.pair do |s1, s2|
s2.close assert_equal(["AF_UNIX", ""], s1.peeraddr)
assert_equal(["AF_UNIX", ""], s2.peeraddr)
end
end end
def test_noname_unpack_sockaddr_un def test_noname_unpack_sockaddr_un
s1, s2 = UNIXSocket.pair if /mswin|mingw/ =~ RUBY_PLATFORM
n = nil omit "unnamed pipe is emulated on windows"
assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != "" end
assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != ""
assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getsockname) != "" UNIXSocket.pair do |s1, s2|
assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getpeername) != "" n = nil
assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getpeername) != "" assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != ""
ensure assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getsockname) != ""
s1.close assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getsockname) != ""
s2.close assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s1.getpeername) != ""
assert_equal("", Socket.unpack_sockaddr_un(n)) if (n = s2.getpeername) != ""
end
end end
def test_noname_recvfrom def test_noname_recvfrom
s1, s2 = UNIXSocket.pair if /mswin|mingw/ =~ RUBY_PLATFORM
s2.write("a") omit "unnamed pipe is emulated on windows"
assert_equal(["a", ["AF_UNIX", ""]], s1.recvfrom(10)) end
ensure
s1.close UNIXSocket.pair do |s1, s2|
s2.close s2.write("a")
assert_equal(["a", ["AF_UNIX", ""]], s1.recvfrom(10))
end
end end
def test_noname_recv_nonblock def test_noname_recv_nonblock
s1, s2 = UNIXSocket.pair UNIXSocket.pair do |s1, s2|
s2.write("a") s2.write("a")
IO.select [s1] IO.select [s1]
assert_equal("a", s1.recv_nonblock(10)) assert_equal("a", s1.recv_nonblock(10))
ensure end
s1.close
s2.close
end end
def test_too_long_path def test_too_long_path
@ -429,12 +439,18 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
rv = s1.recv(100, 0, buf) rv = s1.recv(100, 0, buf)
assert_equal buf.object_id, rv.object_id assert_equal buf.object_id, rv.object_id
assert_equal "BBBBBB", rv assert_equal "BBBBBB", rv
rescue Errno::EPROTOTYPE => error
omit error.message
ensure ensure
s1.close if s1 s1.close if s1
s2.close if s2 s2.close if s2
end end
def test_dgram_pair_sendrecvmsg_errno_set 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) s1, s2 = to_close = UNIXSocket.pair(Socket::SOCK_DGRAM)
pipe = IO.pipe pipe = IO.pipe
to_close.concat(pipe) to_close.concat(pipe)
@ -457,9 +473,17 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
end end
def test_epipe # [ruby-dev:34619] 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| UNIXSocket.pair {|s1, s2|
s1.shutdown(Socket::SHUT_WR) s1.shutdown(Socket::SHUT_WR)
assert_raise(Errno::EPIPE) { s1.write "a" } assert_raise(error) { s1.write "a" }
assert_equal(nil, s2.read(1)) assert_equal(nil, s2.read(1))
s2.write "a" s2.write "a"
assert_equal("a", s1.read(1)) assert_equal("a", s1.read(1))
@ -493,6 +517,45 @@ class TestSocket_UNIXSocket < Test::Unit::TestCase
} }
end 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 def test_initialize
Dir.mktmpdir {|d| Dir.mktmpdir {|d|
Socket.open(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) {|s| Socket.open(Socket::AF_UNIX, Socket::SOCK_STREAM, 0) {|s|

View File

@ -629,6 +629,9 @@ $(CONFIG_H): $(MKFILES) $(srcdir)/win32/Makefile.sub $(win_srcdir)/Makefile.sub
#define HAVE_STDDEF_H 1 #define HAVE_STDDEF_H 1
#define HAVE_STRING_H 1 #define HAVE_STRING_H 1
#define HAVE_MEMORY_H 1 #define HAVE_MEMORY_H 1
!if $(MSC_VER) >= 1920
#define HAVE_AFUNIX_H 1
!endif
!if $(MSC_VER) >= 1400 !if $(MSC_VER) >= 1400
#define HAVE_LONG_LONG 1 #define HAVE_LONG_LONG 1
!else !else

View File

@ -1,6 +1,10 @@
#ifndef RUBY_WIN32_FILE_H #ifndef RUBY_WIN32_FILE_H
#define RUBY_WIN32_FILE_H #define RUBY_WIN32_FILE_H
#ifndef IO_REPARSE_TAG_AF_UNIX
# define IO_REPARSE_TAG_AF_UNIX 0x80000023
#endif
enum { enum {
MINIMUM_REPARSE_BUFFER_PATH_LEN = 100 MINIMUM_REPARSE_BUFFER_PATH_LEN = 100
}; };

View File

@ -49,6 +49,9 @@
#ifdef __MINGW32__ #ifdef __MINGW32__
#include <mswsock.h> #include <mswsock.h>
#endif #endif
#ifdef HAVE_AFUNIX_H
# include <afunix.h>
#endif
#include "ruby/win32.h" #include "ruby/win32.h"
#include "ruby/vm.h" #include "ruby/vm.h"
#include "win32/dir.h" #include "win32/dir.h"
@ -4018,15 +4021,93 @@ rb_w32_getservbyport(int port, const char *proto)
return r; 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 */ /* License: Ruby's */
static int static int
socketpair_internal(int af, int type, int protocol, SOCKET *sv) socketpair_internal(int af, int type, int protocol, SOCKET *sv)
{ {
SOCKET svr = INVALID_SOCKET, r = INVALID_SOCKET, w = INVALID_SOCKET; SOCKET svr = INVALID_SOCKET, r = INVALID_SOCKET, w = INVALID_SOCKET;
struct sockaddr_in sock_in4; struct sockaddr_in sock_in4;
#ifdef INET6 #ifdef INET6
struct sockaddr_in6 sock_in6; struct sockaddr_in6 sock_in6;
#endif #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; struct sockaddr *addr;
int ret = -1; int ret = -1;
int len; int len;
@ -4050,6 +4131,15 @@ socketpair_internal(int af, int type, int protocol, SOCKET *sv)
addr = (struct sockaddr *)&sock_in6; addr = (struct sockaddr *)&sock_in6;
len = sizeof(sock_in6); len = sizeof(sock_in6);
break; 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 #endif
default: default:
errno = EAFNOSUPPORT; errno = EAFNOSUPPORT;
@ -4101,6 +4191,10 @@ socketpair_internal(int af, int type, int protocol, SOCKET *sv)
} }
if (svr != INVALID_SOCKET) if (svr != INVALID_SOCKET)
closesocket(svr); closesocket(svr);
#ifdef HAVE_AFUNIX_H
if (sock_un.sun_family == AF_UNIX)
DeleteFileW(wpath);
#endif
} }
return ret; return ret;
@ -5632,10 +5726,8 @@ fileattr_to_unixmode(DWORD attr, const WCHAR *path, unsigned mode)
/* format is already set */ /* format is already set */
} }
else if (attr & FILE_ATTRIBUTE_REPARSE_POINT) { else if (attr & FILE_ATTRIBUTE_REPARSE_POINT) {
if (rb_w32_reparse_symlink_p(path)) /* Only used by stat_by_find in the case the file can not be opened.
mode |= S_IFLNK | S_IEXEC; * In this case we can't get more details. */
else
mode |= S_IFDIR | S_IEXEC;
} }
else if (attr & FILE_ATTRIBUTE_DIRECTORY) { else if (attr & FILE_ATTRIBUTE_DIRECTORY) {
mode |= S_IFDIR | S_IEXEC; mode |= S_IFDIR | S_IEXEC;
@ -5710,14 +5802,6 @@ stat_by_find(const WCHAR *path, struct stati128 *st)
{ {
HANDLE h; HANDLE h;
WIN32_FIND_DATAW wfd; 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 */ /* Fall back to FindFirstFile for ERROR_SHARING_VIOLATION */
h = FindFirstFileW(path, &wfd); 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; DWORD flags = lstat ? FILE_FLAG_OPEN_REPARSE_POINT : 0;
HANDLE f; HANDLE f;
WCHAR finalname[PATH_MAX]; WCHAR finalname[PATH_MAX];
int open_error;
memset(st, 0, sizeof(*st)); memset(st, 0, sizeof(*st));
f = open_special(path, 0, flags); 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) { if (f != INVALID_HANDLE_VALUE) {
DWORD attr = stati128_handle(f, st); DWORD attr = stati128_handle(f, st);
const DWORD len = get_final_path(f, finalname, numberof(finalname), 0); 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: case FILE_TYPE_PIPE:
mode = S_IFIFO; mode = S_IFIFO;
break; 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); 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 (attr & FILE_ATTRIBUTE_DIRECTORY) {
if (check_valid_dir(path)) return -1; if (check_valid_dir(path)) return -1;
} }
@ -5788,6 +5898,12 @@ winnt_stat(const WCHAR *path, struct stati128 *st, BOOL lstat)
} }
} }
else { 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; if (stat_by_find(path, st)) return -1;
} }