mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
net,os: support converting between *os.File and net.Conn on Windows
The runtime poller and os.NewFile recently gained support for disassociating the handle from the runtime poller IOCP (see CL 664455). This was the main blocker for allowing the conversion between *os.File and net.Conn. Implementing the conversion is now trivial. The only remaining work, implemented in this CL, is improving os.NewFile to also support socket handles and updating some build tags so that Windows can share almost the same net's File implementation as Unix. There is one important limitation, though: the duplicated socket handle returned by the various File methods in the net package is not usable on other process. If someone needs to pass a socket handle to another process, they should manually call the WSADuplicateSocket Windows API passing the process ID of the target process. Fixes #9503. Fixes #10350. Updates #19098. Cq-Include-Trybots: luci.golang.try:gotip-windows-amd64-race,gotip-windows-amd64-longtest,gotip-windows-arm64 Change-Id: Ic43cadaac2662b925d57a9d362ddc7ae21d1b56e Reviewed-on: https://go-review.googlesource.com/c/go/+/668195 Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Carlos Amedee <carlos@golang.org>
This commit is contained in:
parent
8ec555931d
commit
6953ef86cd
3
doc/next/6-stdlib/99-minor/net/10350.md
Normal file
3
doc/next/6-stdlib/99-minor/net/10350.md
Normal file
@ -0,0 +1,3 @@
|
||||
On Windows, the [TCPConn.File], [UDPConn.File], [UnixConn.File],
|
||||
[IPConn.File], [TCPListener.File], and [UnixListener.File]
|
||||
methods are now supported.
|
2
doc/next/6-stdlib/99-minor/net/9503.md
Normal file
2
doc/next/6-stdlib/99-minor/net/9503.md
Normal file
@ -0,0 +1,2 @@
|
||||
On Windows, the [FileConn], [FilePacketConn], [FileListener]
|
||||
functions are now supported.
|
@ -18,6 +18,7 @@ func WSASendtoInet4(s syscall.Handle, bufs *syscall.WSABuf, bufcnt uint32, sent
|
||||
func WSASendtoInet6(s syscall.Handle, bufs *syscall.WSABuf, bufcnt uint32, sent *uint32, flags uint32, to *syscall.SockaddrInet6, overlapped *syscall.Overlapped, croutine *byte) (err error)
|
||||
|
||||
const (
|
||||
SO_TYPE = 0x1008
|
||||
SIO_TCP_INITIAL_RTO = syscall.IOC_IN | syscall.IOC_VENDOR | 17
|
||||
TCP_INITIAL_RTO_UNSPECIFIED_RTT = ^uint16(0)
|
||||
TCP_INITIAL_RTO_NO_SYN_RETRANSMISSIONS = ^uint8(1)
|
||||
|
@ -268,6 +268,7 @@ type WSAMsg struct {
|
||||
}
|
||||
|
||||
//sys WSASocket(af int32, typ int32, protocol int32, protinfo *syscall.WSAProtocolInfo, group uint32, flags uint32) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = ws2_32.WSASocketW
|
||||
//sys WSADuplicateSocket(s syscall.Handle, processID uint32, info *syscall.WSAProtocolInfo) (err error) [failretval!=0] = ws2_32.WSADuplicateSocketW
|
||||
//sys WSAGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||
|
||||
func loadWSASendRecvMsg() error {
|
||||
|
@ -106,6 +106,7 @@ var (
|
||||
procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
|
||||
procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock")
|
||||
procGetProfilesDirectoryW = moduserenv.NewProc("GetProfilesDirectoryW")
|
||||
procWSADuplicateSocketW = modws2_32.NewProc("WSADuplicateSocketW")
|
||||
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
|
||||
procWSASocketW = modws2_32.NewProc("WSASocketW")
|
||||
)
|
||||
@ -591,6 +592,14 @@ func GetProfilesDirectory(dir *uint16, dirLen *uint32) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func WSADuplicateSocket(s syscall.Handle, processID uint32, info *syscall.WSAProtocolInfo) (err error) {
|
||||
r1, _, e1 := syscall.Syscall(procWSADuplicateSocketW.Addr(), 3, uintptr(s), uintptr(processID), uintptr(unsafe.Pointer(info)))
|
||||
if r1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func WSAGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
|
||||
var _p0 uint32
|
||||
if wait {
|
||||
|
@ -736,11 +736,6 @@ third:
|
||||
}
|
||||
|
||||
func TestFileError(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
t.Skipf("not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp("", "go-nettest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -26,6 +26,17 @@ type netFD struct {
|
||||
raddr Addr
|
||||
}
|
||||
|
||||
func (fd *netFD) name() string {
|
||||
var ls, rs string
|
||||
if fd.laddr != nil {
|
||||
ls = fd.laddr.String()
|
||||
}
|
||||
if fd.raddr != nil {
|
||||
rs = fd.raddr.String()
|
||||
}
|
||||
return fd.net + ":" + ls + "->" + rs
|
||||
}
|
||||
|
||||
func (fd *netFD) setAddr(laddr, raddr Addr) {
|
||||
fd.laddr = laddr
|
||||
fd.raddr = raddr
|
||||
|
@ -41,17 +41,6 @@ func (fd *netFD) init() error {
|
||||
return fd.pfd.Init(fd.net, true)
|
||||
}
|
||||
|
||||
func (fd *netFD) name() string {
|
||||
var ls, rs string
|
||||
if fd.laddr != nil {
|
||||
ls = fd.laddr.String()
|
||||
}
|
||||
if fd.raddr != nil {
|
||||
rs = fd.raddr.String()
|
||||
}
|
||||
return fd.net + ":" + ls + "->" + rs
|
||||
}
|
||||
|
||||
func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (rsa syscall.Sockaddr, ret error) {
|
||||
// Do not need to call fd.writeLock here,
|
||||
// because fd is not yet accessible to user,
|
||||
|
@ -233,9 +233,23 @@ func (fd *netFD) accept() (*netFD, error) {
|
||||
return netfd, nil
|
||||
}
|
||||
|
||||
// Unimplemented functions.
|
||||
|
||||
func (fd *netFD) dup() (*os.File, error) {
|
||||
// TODO: Implement this, perhaps using internal/poll.DupCloseOnExec.
|
||||
return nil, syscall.EWINDOWS
|
||||
// Disassociate the IOCP from the socket,
|
||||
// it is not safe to share a duplicated handle
|
||||
// that is associated with IOCP.
|
||||
if err := fd.pfd.DisassociateIOCP(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var h syscall.Handle
|
||||
var syserr error
|
||||
err := fd.pfd.RawControl(func(fd uintptr) {
|
||||
h, syserr = dupSocket(syscall.Handle(fd))
|
||||
})
|
||||
if err != nil {
|
||||
err = syserr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return os.NewFile(uintptr(h), fd.name()), nil
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ package net
|
||||
|
||||
import "os"
|
||||
|
||||
// BUG(mikio): On JS and Windows, the FileConn, FileListener and
|
||||
// BUG(mikio): On JS, the FileConn, FileListener and
|
||||
// FilePacketConn functions are not implemented.
|
||||
|
||||
type fileAddr string
|
||||
|
104
src/net/file_posix.go
Normal file
104
src/net/file_posix.go
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright 2025 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build unix || windows
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"internal/poll"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func newFileFD(f *os.File) (*netFD, error) {
|
||||
s, err := dupFileSocket(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
family := syscall.AF_UNSPEC
|
||||
sotype, err := syscall.GetsockoptInt(s, syscall.SOL_SOCKET, _SO_TYPE)
|
||||
if err != nil {
|
||||
poll.CloseFunc(s)
|
||||
return nil, os.NewSyscallError("getsockopt", err)
|
||||
}
|
||||
lsa, _ := syscall.Getsockname(s)
|
||||
rsa, _ := syscall.Getpeername(s)
|
||||
switch lsa.(type) {
|
||||
case *syscall.SockaddrInet4:
|
||||
family = syscall.AF_INET
|
||||
case *syscall.SockaddrInet6:
|
||||
family = syscall.AF_INET6
|
||||
case *syscall.SockaddrUnix:
|
||||
family = syscall.AF_UNIX
|
||||
default:
|
||||
poll.CloseFunc(s)
|
||||
return nil, syscall.EPROTONOSUPPORT
|
||||
}
|
||||
fd, err := newFD(s, family, sotype, "")
|
||||
if err != nil {
|
||||
poll.CloseFunc(s)
|
||||
return nil, err
|
||||
}
|
||||
laddr := fd.addrFunc()(lsa)
|
||||
raddr := fd.addrFunc()(rsa)
|
||||
fd.net = laddr.Network()
|
||||
if err := fd.init(); err != nil {
|
||||
fd.Close()
|
||||
return nil, err
|
||||
}
|
||||
fd.setAddr(laddr, raddr)
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
func fileConn(f *os.File) (Conn, error) {
|
||||
fd, err := newFileFD(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch fd.laddr.(type) {
|
||||
case *TCPAddr:
|
||||
return newTCPConn(fd, defaultTCPKeepAliveIdle, KeepAliveConfig{}, testPreHookSetKeepAlive, testHookSetKeepAlive), nil
|
||||
case *UDPAddr:
|
||||
return newUDPConn(fd), nil
|
||||
case *IPAddr:
|
||||
return newIPConn(fd), nil
|
||||
case *UnixAddr:
|
||||
return newUnixConn(fd), nil
|
||||
}
|
||||
fd.Close()
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
|
||||
func fileListener(f *os.File) (Listener, error) {
|
||||
fd, err := newFileFD(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch laddr := fd.laddr.(type) {
|
||||
case *TCPAddr:
|
||||
return &TCPListener{fd: fd}, nil
|
||||
case *UnixAddr:
|
||||
return &UnixListener{fd: fd, path: laddr.Name, unlink: false}, nil
|
||||
}
|
||||
fd.Close()
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
|
||||
func filePacketConn(f *os.File) (PacketConn, error) {
|
||||
fd, err := newFileFD(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch fd.laddr.(type) {
|
||||
case *UDPAddr:
|
||||
return newUDPConn(fd), nil
|
||||
case *IPAddr:
|
||||
return newIPConn(fd), nil
|
||||
case *UnixAddr:
|
||||
return newUnixConn(fd), nil
|
||||
}
|
||||
fd.Close()
|
||||
return nil, syscall.EINVAL
|
||||
}
|
@ -29,7 +29,7 @@ var fileConnTests = []struct {
|
||||
|
||||
func TestFileConn(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "plan9", "windows", "js", "wasip1":
|
||||
case "plan9", "js", "wasip1":
|
||||
t.Skipf("not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
@ -130,7 +130,7 @@ var fileListenerTests = []struct {
|
||||
|
||||
func TestFileListener(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "plan9", "windows", "js", "wasip1":
|
||||
case "plan9", "js", "wasip1":
|
||||
t.Skipf("not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
@ -222,7 +222,7 @@ var filePacketConnTests = []struct {
|
||||
|
||||
func TestFilePacketConn(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "plan9", "windows", "js", "wasip1":
|
||||
case "plan9", "js", "wasip1":
|
||||
t.Skipf("not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
@ -289,7 +289,7 @@ func TestFilePacketConn(t *testing.T) {
|
||||
// Issue 24483.
|
||||
func TestFileCloseRace(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "plan9", "windows", "js", "wasip1":
|
||||
case "plan9", "js", "wasip1":
|
||||
t.Skipf("not supported on %s", runtime.GOOS)
|
||||
}
|
||||
if !testableNetwork("tcp") {
|
||||
|
@ -12,7 +12,9 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func dupSocket(f *os.File) (int, error) {
|
||||
const _SO_TYPE = syscall.SO_TYPE
|
||||
|
||||
func dupFileSocket(f *os.File) (int, error) {
|
||||
s, call, err := poll.DupCloseOnExec(int(f.Fd()))
|
||||
if err != nil {
|
||||
if call != "" {
|
||||
@ -26,94 +28,3 @@ func dupSocket(f *os.File) (int, error) {
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func newFileFD(f *os.File) (*netFD, error) {
|
||||
s, err := dupSocket(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
family := syscall.AF_UNSPEC
|
||||
sotype, err := syscall.GetsockoptInt(s, syscall.SOL_SOCKET, syscall.SO_TYPE)
|
||||
if err != nil {
|
||||
poll.CloseFunc(s)
|
||||
return nil, os.NewSyscallError("getsockopt", err)
|
||||
}
|
||||
lsa, _ := syscall.Getsockname(s)
|
||||
rsa, _ := syscall.Getpeername(s)
|
||||
switch lsa.(type) {
|
||||
case *syscall.SockaddrInet4:
|
||||
family = syscall.AF_INET
|
||||
case *syscall.SockaddrInet6:
|
||||
family = syscall.AF_INET6
|
||||
case *syscall.SockaddrUnix:
|
||||
family = syscall.AF_UNIX
|
||||
default:
|
||||
poll.CloseFunc(s)
|
||||
return nil, syscall.EPROTONOSUPPORT
|
||||
}
|
||||
fd, err := newFD(s, family, sotype, "")
|
||||
if err != nil {
|
||||
poll.CloseFunc(s)
|
||||
return nil, err
|
||||
}
|
||||
laddr := fd.addrFunc()(lsa)
|
||||
raddr := fd.addrFunc()(rsa)
|
||||
fd.net = laddr.Network()
|
||||
if err := fd.init(); err != nil {
|
||||
fd.Close()
|
||||
return nil, err
|
||||
}
|
||||
fd.setAddr(laddr, raddr)
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
func fileConn(f *os.File) (Conn, error) {
|
||||
fd, err := newFileFD(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch fd.laddr.(type) {
|
||||
case *TCPAddr:
|
||||
return newTCPConn(fd, defaultTCPKeepAliveIdle, KeepAliveConfig{}, testPreHookSetKeepAlive, testHookSetKeepAlive), nil
|
||||
case *UDPAddr:
|
||||
return newUDPConn(fd), nil
|
||||
case *IPAddr:
|
||||
return newIPConn(fd), nil
|
||||
case *UnixAddr:
|
||||
return newUnixConn(fd), nil
|
||||
}
|
||||
fd.Close()
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
|
||||
func fileListener(f *os.File) (Listener, error) {
|
||||
fd, err := newFileFD(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch laddr := fd.laddr.(type) {
|
||||
case *TCPAddr:
|
||||
return &TCPListener{fd: fd}, nil
|
||||
case *UnixAddr:
|
||||
return &UnixListener{fd: fd, path: laddr.Name, unlink: false}, nil
|
||||
}
|
||||
fd.Close()
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
|
||||
func filePacketConn(f *os.File) (PacketConn, error) {
|
||||
fd, err := newFileFD(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch fd.laddr.(type) {
|
||||
case *UDPAddr:
|
||||
return newUDPConn(fd), nil
|
||||
case *IPAddr:
|
||||
return newIPConn(fd), nil
|
||||
case *UnixAddr:
|
||||
return newUnixConn(fd), nil
|
||||
}
|
||||
fd.Close()
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
|
@ -5,21 +5,28 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"internal/syscall/windows"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func fileConn(f *os.File) (Conn, error) {
|
||||
// TODO: Implement this
|
||||
return nil, syscall.EWINDOWS
|
||||
const _SO_TYPE = windows.SO_TYPE
|
||||
|
||||
func dupSocket(h syscall.Handle) (syscall.Handle, error) {
|
||||
var info syscall.WSAProtocolInfo
|
||||
err := windows.WSADuplicateSocket(h, uint32(syscall.Getpid()), &info)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return windows.WSASocket(-1, -1, -1, &info, 0, windows.WSA_FLAG_OVERLAPPED|windows.WSA_FLAG_NO_HANDLE_INHERIT)
|
||||
}
|
||||
|
||||
func fileListener(f *os.File) (Listener, error) {
|
||||
// TODO: Implement this
|
||||
return nil, syscall.EWINDOWS
|
||||
}
|
||||
|
||||
func filePacketConn(f *os.File) (PacketConn, error) {
|
||||
// TODO: Implement this
|
||||
return nil, syscall.EWINDOWS
|
||||
func dupFileSocket(f *os.File) (syscall.Handle, error) {
|
||||
// The resulting handle should not be associated to an IOCP, else the IO operations
|
||||
// will block an OS thread, and that's not what net package users expect.
|
||||
h, err := dupSocket(syscall.Handle(f.Fd()))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
package net
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func installTestHooks() {}
|
||||
|
||||
func uninstallTestHooks() {}
|
||||
@ -14,3 +16,5 @@ func forceCloseSockets() {}
|
||||
func enableSocketConnect() {}
|
||||
|
||||
func disableSocketConnect(network string) {}
|
||||
|
||||
func addCmdInheritedHandle(cmd *exec.Cmd, fd uintptr) {}
|
||||
|
@ -6,7 +6,10 @@
|
||||
|
||||
package net
|
||||
|
||||
import "internal/poll"
|
||||
import (
|
||||
"internal/poll"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var (
|
||||
// Placeholders for saving original socket system calls.
|
||||
@ -53,3 +56,5 @@ func forceCloseSockets() {
|
||||
poll.CloseFunc(s)
|
||||
}
|
||||
}
|
||||
|
||||
func addCmdInheritedHandle(cmd *exec.Cmd, fd uintptr) {}
|
||||
|
@ -6,8 +6,12 @@
|
||||
|
||||
package net
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func installTestHooks() {}
|
||||
|
||||
func uninstallTestHooks() {}
|
||||
|
||||
func forceCloseSockets() {}
|
||||
|
||||
func addCmdInheritedHandle(cmd *exec.Cmd, fd uintptr) {}
|
||||
|
@ -4,7 +4,11 @@
|
||||
|
||||
package net
|
||||
|
||||
import "internal/poll"
|
||||
import (
|
||||
"internal/poll"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
// Placeholders for saving original socket system calls.
|
||||
@ -40,3 +44,14 @@ func forceCloseSockets() {
|
||||
poll.CloseFunc(s)
|
||||
}
|
||||
}
|
||||
|
||||
func addCmdInheritedHandle(cmd *exec.Cmd, fd uintptr) {
|
||||
// Inherited handles are not inherited by default in Windows.
|
||||
// We need to set the handle inheritance flag explicitly.
|
||||
// See https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa#parameters
|
||||
// for more details.
|
||||
if cmd.SysProcAttr == nil {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
cmd.SysProcAttr.AdditionalInheritedHandles = append(cmd.SysProcAttr.AdditionalInheritedHandles, syscall.Handle(fd))
|
||||
}
|
||||
|
@ -509,6 +509,10 @@ func packetTransceiver(c PacketConn, wb []byte, dst Addr, ch chan<- error) {
|
||||
func spawnTestSocketPair(t testing.TB, net string) (client, server Conn) {
|
||||
t.Helper()
|
||||
|
||||
if !testableNetwork(net) {
|
||||
t.Skipf("network %q not supported", net)
|
||||
}
|
||||
|
||||
ln := newLocalListener(t, net)
|
||||
defer ln.Close()
|
||||
var cerr, serr error
|
||||
@ -536,13 +540,6 @@ func spawnTestSocketPair(t testing.TB, net string) (client, server Conn) {
|
||||
|
||||
func startTestSocketPeer(t testing.TB, conn Conn, op string, chunkSize, totalSize int) (func(t testing.TB), error) {
|
||||
t.Helper()
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// TODO(panjf2000): Windows has not yet implemented FileConn,
|
||||
// remove this when it's implemented in https://go.dev/issues/9503.
|
||||
t.Fatalf("startTestSocketPeer is not supported on %s", runtime.GOOS)
|
||||
}
|
||||
|
||||
f, err := conn.(interface{ File() (*os.File, error) }).File()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -556,7 +553,14 @@ func startTestSocketPeer(t testing.TB, conn Conn, op string, chunkSize, totalSiz
|
||||
"GO_NET_TEST_TRANSFER_TOTAL_SIZE=" + strconv.Itoa(totalSize),
|
||||
"TMPDIR=" + os.Getenv("TMPDIR"),
|
||||
}
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows doesn't support ExtraFiles
|
||||
fd := f.Fd()
|
||||
cmd.Env = append(cmd.Env, "GO_NET_TEST_TRANSFER_FD="+strconv.FormatUint(uint64(fd), 10))
|
||||
addCmdInheritedHandle(cmd, fd)
|
||||
} else {
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, f)
|
||||
}
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
@ -586,7 +590,17 @@ func init() {
|
||||
}
|
||||
defer os.Exit(0)
|
||||
|
||||
f := os.NewFile(uintptr(3), "splice-test-conn")
|
||||
var fd uintptr
|
||||
if runtime.GOOS == "windows" {
|
||||
v, err := strconv.ParseUint(os.Getenv("GO_NET_TEST_TRANSFER_FD"), 10, 0)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fd = uintptr(v)
|
||||
} else {
|
||||
fd = uintptr(3)
|
||||
}
|
||||
f := os.NewFile(fd, "splice-test-conn")
|
||||
defer f.Close()
|
||||
|
||||
conn, err := FileConn(f)
|
||||
|
@ -308,6 +308,9 @@ func (c *conn) SetWriteBuffer(bytes int) error {
|
||||
// The returned os.File's file descriptor is different from the connection's.
|
||||
// Attempting to change properties of the original using this duplicate
|
||||
// may or may not have the desired effect.
|
||||
//
|
||||
// On Windows, the returned os.File's file descriptor is not usable
|
||||
// on other processes.
|
||||
func (c *conn) File() (f *os.File, err error) {
|
||||
f, err = c.fd.dup()
|
||||
if err != nil {
|
||||
|
@ -417,6 +417,9 @@ func (l *TCPListener) SetDeadline(t time.Time) error {
|
||||
// The returned os.File's file descriptor is different from the
|
||||
// connection's. Attempting to change properties of the original
|
||||
// using this duplicate may or may not have the desired effect.
|
||||
//
|
||||
// On Windows, the returned os.File's file descriptor is not
|
||||
// usable on other processes.
|
||||
func (l *TCPListener) File() (f *os.File, err error) {
|
||||
if !l.ok() {
|
||||
return nil, syscall.EINVAL
|
||||
|
@ -297,6 +297,9 @@ func (l *UnixListener) SetDeadline(t time.Time) error {
|
||||
// The returned [os.File]'s file descriptor is different from the
|
||||
// connection's. Attempting to change properties of the original
|
||||
// using this duplicate may or may not have the desired effect.
|
||||
//
|
||||
// On Windows, the returned os.File's file descriptor is not
|
||||
// usable on other processes.
|
||||
func (l *UnixListener) File() (f *os.File, err error) {
|
||||
if !l.ok() {
|
||||
return nil, syscall.EINVAL
|
||||
|
@ -398,9 +398,6 @@ func TestUnixUnlink(t *testing.T) {
|
||||
|
||||
// FileListener should not.
|
||||
t.Run("FileListener", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping: FileListener not implemented on windows")
|
||||
}
|
||||
l := listen(t)
|
||||
f, _ := l.File()
|
||||
l1, _ := FileListener(f)
|
||||
@ -448,9 +445,6 @@ func TestUnixUnlink(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("FileListener/SetUnlinkOnClose(true)", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping: FileListener not implemented on windows")
|
||||
}
|
||||
l := listen(t)
|
||||
f, _ := l.File()
|
||||
l1, _ := FileListener(f)
|
||||
@ -464,9 +458,6 @@ func TestUnixUnlink(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("FileListener/SetUnlinkOnClose(false)", func(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("skipping: FileListener not implemented on windows")
|
||||
}
|
||||
l := listen(t)
|
||||
f, _ := l.File()
|
||||
l1, _ := FileListener(f)
|
||||
|
@ -82,14 +82,49 @@ func newConsoleFile(h syscall.Handle, name string) *File {
|
||||
return newFile(h, name, "console", false)
|
||||
}
|
||||
|
||||
var wsaLoaded atomic.Bool
|
||||
|
||||
// isWSALoaded returns true if the ws2_32.dll module is loaded.
|
||||
func isWSALoaded() bool {
|
||||
// ws2_32.dll may be delay loaded, we can only short-circuit
|
||||
// if we know it is loaded.
|
||||
if wsaLoaded.Load() {
|
||||
return true
|
||||
}
|
||||
var ws2_32_dll = [...]uint16{'w', 's', '2', '_', '3', '2', '.', 'd', 'l', 'l', 0}
|
||||
_, err := windows.GetModuleHandle(unsafe.SliceData(ws2_32_dll[:]))
|
||||
wsaLoaded.Store(err == nil)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// newFileFromNewFile is called by [NewFile].
|
||||
func newFileFromNewFile(fd uintptr, name string) *File {
|
||||
h := syscall.Handle(fd)
|
||||
if h == syscall.InvalidHandle {
|
||||
return nil
|
||||
}
|
||||
kind := "file"
|
||||
var sotype int
|
||||
if t, err := syscall.GetFileType(h); err == nil && t == syscall.FILE_TYPE_PIPE {
|
||||
kind = "pipe"
|
||||
// Windows reports sockets as FILE_TYPE_PIPE.
|
||||
// We need to call getsockopt and check the socket type to distinguish between sockets and pipes.
|
||||
// If the call fails, we assume it's a pipe.
|
||||
// Avoid calling getsockopt if the WSA module is not loaded, it is a heavy dependency
|
||||
// and sockets can only be created using that module.
|
||||
if isWSALoaded() {
|
||||
if sotype, err = syscall.GetsockoptInt(h, syscall.SOL_SOCKET, windows.SO_TYPE); err == nil {
|
||||
kind = "net"
|
||||
}
|
||||
}
|
||||
}
|
||||
nonBlocking, _ := windows.IsNonblock(syscall.Handle(fd))
|
||||
return newFile(h, name, "file", nonBlocking)
|
||||
f := newFile(h, name, kind, nonBlocking)
|
||||
if kind == "net" {
|
||||
f.pfd.IsStream = sotype == syscall.SOCK_STREAM
|
||||
f.pfd.ZeroReadIsEOF = sotype != syscall.SOCK_DGRAM && sotype != syscall.SOCK_RAW
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func epipecheck(file *File, e error) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user