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 коммит произвёл GitHub
Родитель 4e4b29b1a9
Коммит ea8a7287e2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
24 изменённых файлов: 340 добавлений и 125 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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)

Просмотреть файл

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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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:

Просмотреть файл

@ -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

Просмотреть файл

@ -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);

Просмотреть файл

@ -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);

Просмотреть файл

@ -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
*

Просмотреть файл

@ -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;
@ -42,6 +42,10 @@ unixsock_path_value(VALUE path)
return name; /* ignore encoding */
}
}
#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
*

3
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;

Просмотреть файл

@ -18,11 +18,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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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)

Просмотреть файл

@ -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")

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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|

Просмотреть файл

@ -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

Просмотреть файл

@ -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
};

Просмотреть файл

@ -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;
@ -4050,6 +4131,15 @@ socketpair_internal(int af, int type, int protocol, SOCKET *sv)
addr = (struct sockaddr *)&sock_in6;
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;
@ -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;
}