From 1909418bb38aacd2c4d0bc43bfeef4903f1ece17 Mon Sep 17 00:00:00 2001 From: Gaukas Wang Date: Sat, 30 Dec 2023 22:00:08 -0700 Subject: [PATCH] fix: wasi fd subject to garbage collection on unix commit 81e41c54676845d205be92aa56703cc15e476041 Author: Edoardo Vacchi Date: Thu Dec 28 11:07:28 2023 +0100 wasi: sockets, share most logic across Windows and Unix Signed-off-by: Edoardo Vacchi commit 4f9f56904717c7472060e71fb74214de7654392b Author: Edoardo Vacchi Date: Thu Dec 28 10:10:06 2023 +0100 wasi: sockets, use the same strategy for impl as Windows Signed-off-by: Edoardo Vacchi --- internal/sysfs/file_windows.go | 4 +- internal/sysfs/sock.go | 195 +++++++++++++++++++++++++ internal/sysfs/sock_supported.go | 29 ++++ internal/sysfs/sock_unix.go | 173 +++-------------------- internal/sysfs/sock_unsupported.go | 33 +++++ internal/sysfs/sock_windows.go | 219 +---------------------------- 6 files changed, 287 insertions(+), 366 deletions(-) create mode 100644 internal/sysfs/sock_supported.go diff --git a/internal/sysfs/file_windows.go b/internal/sysfs/file_windows.go index 3ad9648e65..0658c82042 100644 --- a/internal/sysfs/file_windows.go +++ b/internal/sysfs/file_windows.go @@ -46,10 +46,10 @@ func writeFd(fd uintptr, buf []byte) (int, sys.Errno) { return -1, sys.ENOSYS } -func readSocket(h syscall.Handle, buf []byte) (int, sys.Errno) { +func readSocket(h uintptr, buf []byte) (int, sys.Errno) { var overlapped syscall.Overlapped var done uint32 - errno := syscall.ReadFile(h, buf, &done, &overlapped) + errno := syscall.ReadFile(syscall.Handle(h), buf, &done, &overlapped) if errno == syscall.ERROR_IO_PENDING { errno = sys.EAGAIN } diff --git a/internal/sysfs/sock.go b/internal/sysfs/sock.go index af739a9082..8f8e6def5b 100644 --- a/internal/sysfs/sock.go +++ b/internal/sysfs/sock.go @@ -5,6 +5,7 @@ import ( "os" experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" socketapi "github.com/tetratelabs/wazero/internal/sock" "github.com/tetratelabs/wazero/sys" ) @@ -35,3 +36,197 @@ func (f *baseSockFile) Stat() (fs sys.Stat_t, errno experimentalsys.Errno) { fs.Mode = os.ModeIrregular return } + +var _ socketapi.TCPSock = (*tcpListenerFile)(nil) + +type tcpListenerFile struct { + baseSockFile + + tl *net.TCPListener + closed bool + nonblock bool +} + +// newTCPListenerFile is a constructor for a socketapi.TCPSock. +// +// The current strategy is to wrap a net.TCPListener +// and invoking raw syscalls using syscallConnControl: +// this internal calls RawConn.Control(func(fd)), making sure +// that the underlying file descriptor is valid throughout +// the duration of the syscall. +func newDefaultTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { + return &tcpListenerFile{tl: tl} +} + +// Accept implements the same method as documented on socketapi.TCPSock +func (f *tcpListenerFile) Accept() (socketapi.TCPConn, experimentalsys.Errno) { + // Ensure we have an incoming connection, otherwise return immediately. + if f.nonblock { + if ready, errno := _pollSock(f.tl, fsapi.POLLIN, 0); !ready || errno != 0 { + return nil, experimentalsys.EAGAIN + } + } + + // Accept normally blocks goroutines, but we + // made sure that we have an incoming connection, + // so we should be safe. + if conn, err := f.tl.Accept(); err != nil { + return nil, experimentalsys.UnwrapOSError(err) + } else { + return newTcpConn(conn.(*net.TCPConn)), 0 + } +} + +// Close implements the same method as documented on experimentalsys.File +func (f *tcpListenerFile) Close() experimentalsys.Errno { + if !f.closed { + return experimentalsys.UnwrapOSError(f.tl.Close()) + } + return 0 +} + +// Addr is exposed for testing. +func (f *tcpListenerFile) Addr() *net.TCPAddr { + return f.tl.Addr().(*net.TCPAddr) +} + +// SetNonblock implements the same method as documented on fsapi.File +func (f *tcpListenerFile) SetNonblock(enabled bool) (errno experimentalsys.Errno) { + f.nonblock = enabled + _, errno = syscallConnControl(f.tl, func(fd uintptr) (int, experimentalsys.Errno) { + return 0, setNonblockSocket(fd, enabled) + }) + return +} + +// IsNonblock implements the same method as documented on fsapi.File +func (f *tcpListenerFile) IsNonblock() bool { + return f.nonblock +} + +// Poll implements the same method as documented on fsapi.File +func (f *tcpListenerFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} + +var _ socketapi.TCPConn = (*tcpConnFile)(nil) + +type tcpConnFile struct { + baseSockFile + + tc *net.TCPConn + + // nonblock is true when the underlying connection is flagged as non-blocking. + // This ensures that reads and writes return experimentalsys.EAGAIN without blocking the caller. + nonblock bool + // closed is true when closed was called. This ensures proper experimentalsys.EBADF + closed bool +} + +func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { + return &tcpConnFile{tc: tc} +} + +// Read implements the same method as documented on experimentalsys.File +func (f *tcpConnFile) Read(buf []byte) (n int, errno experimentalsys.Errno) { + if len(buf) == 0 { + return 0, 0 // Short-circuit 0-len reads. + } + if nonBlockingFileReadSupported && f.IsNonblock() { + n, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { + n, err := readSocket(fd, buf) + errno = experimentalsys.UnwrapOSError(err) + errno = fileError(f, f.closed, errno) + return n, errno + }) + } else { + n, errno = read(f.tc, buf) + } + if errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Write implements the same method as documented on experimentalsys.File +func (f *tcpConnFile) Write(buf []byte) (n int, errno experimentalsys.Errno) { + if nonBlockingFileWriteSupported && f.IsNonblock() { + return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { + n, err := writeSocket(fd, buf) + errno = experimentalsys.UnwrapOSError(err) + errno = fileError(f, f.closed, errno) + return n, errno + }) + } else { + n, errno = write(f.tc, buf) + } + if errno != 0 { + // Defer validation overhead until we've already had an error. + errno = fileError(f, f.closed, errno) + } + return +} + +// Recvfrom implements the same method as documented on socketapi.TCPConn +func (f *tcpConnFile) Recvfrom(p []byte, flags int) (n int, errno experimentalsys.Errno) { + if flags != MSG_PEEK { + errno = experimentalsys.EINVAL + return + } + return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { + n, err := recvfrom(fd, p, MSG_PEEK) + errno = experimentalsys.UnwrapOSError(err) + errno = fileError(f, f.closed, errno) + return n, errno + }) +} + +// Shutdown implements the same method as documented on experimentalsys.Conn +func (f *tcpConnFile) Shutdown(how int) experimentalsys.Errno { + // FIXME: can userland shutdown listeners? + var err error + switch how { + case socketapi.SHUT_RD: + err = f.tc.CloseRead() + case socketapi.SHUT_WR: + err = f.tc.CloseWrite() + case socketapi.SHUT_RDWR: + return f.close() + default: + return experimentalsys.EINVAL + } + return experimentalsys.UnwrapOSError(err) +} + +// Close implements the same method as documented on experimentalsys.File +func (f *tcpConnFile) Close() experimentalsys.Errno { + return f.close() +} + +func (f *tcpConnFile) close() experimentalsys.Errno { + if f.closed { + return 0 + } + f.closed = true + return f.Shutdown(socketapi.SHUT_RDWR) +} + +// SetNonblock implements the same method as documented on fsapi.File +func (f *tcpConnFile) SetNonblock(enabled bool) (errno experimentalsys.Errno) { + f.nonblock = enabled + _, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { + return 0, experimentalsys.UnwrapOSError(setNonblockSocket(fd, enabled)) + }) + return +} + +// IsNonblock implements the same method as documented on fsapi.File +func (f *tcpConnFile) IsNonblock() bool { + return f.nonblock +} + +// Poll implements the same method as documented on fsapi.File +func (f *tcpConnFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { + return false, experimentalsys.ENOSYS +} diff --git a/internal/sysfs/sock_supported.go b/internal/sysfs/sock_supported.go new file mode 100644 index 0000000000..747393b4fb --- /dev/null +++ b/internal/sysfs/sock_supported.go @@ -0,0 +1,29 @@ +//go:build linux || darwin || windows + +package sysfs + +import ( + "syscall" + + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" +) + +// syscallConnControl extracts a syscall.RawConn from the given syscall.Conn and applies +// the given fn to a file descriptor, returning an integer or a nonzero syscall.Errno on failure. +// +// syscallConnControl streamlines the pattern of extracting the syscall.Rawconn, +// invoking its syscall.RawConn.Control method, then handling properly the errors that may occur +// within fn or returned by syscall.RawConn.Control itself. +func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, experimentalsys.Errno)) (n int, errno experimentalsys.Errno) { + syscallConn, err := conn.SyscallConn() + if err != nil { + return 0, experimentalsys.UnwrapOSError(err) + } + // Prioritize the inner errno over Control + if controlErr := syscallConn.Control(func(fd uintptr) { + n, errno = fn(fd) + }); errno == 0 { + errno = experimentalsys.UnwrapOSError(controlErr) + } + return +} diff --git a/internal/sysfs/sock_unix.go b/internal/sysfs/sock_unix.go index 3698f560e0..0f570f37b7 100644 --- a/internal/sysfs/sock_unix.go +++ b/internal/sysfs/sock_unix.go @@ -14,167 +14,36 @@ import ( // MSG_PEEK is the constant syscall.MSG_PEEK const MSG_PEEK = syscall.MSG_PEEK -// newTCPListenerFile is a constructor for a socketapi.TCPSock. -// -// Note: the implementation of socketapi.TCPSock goes straight -// to the syscall layer, bypassing most of the Go library. -// For an alternative approach, consider winTcpListenerFile -// where most APIs are implemented with regular Go std-lib calls. func newTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { - conn, err := tl.File() - if err != nil { - panic(err) - } - fd := conn.Fd() - // We need to duplicate this file handle, or the lifecycle will be tied - // to the TCPListener. We rely on the TCPListener only to set up - // the connection correctly and parse/resolve the TCP Address - // (notice we actually rely on the listener in the Windows implementation). - sysfd, err := syscall.Dup(int(fd)) - if err != nil { - panic(err) - } - return &tcpListenerFile{fd: uintptr(sysfd), addr: tl.Addr().(*net.TCPAddr)} + return newDefaultTCPListenerFile(tl) } -var _ socketapi.TCPSock = (*tcpListenerFile)(nil) - -type tcpListenerFile struct { - baseSockFile - - fd uintptr - addr *net.TCPAddr - nonblock bool -} - -// Accept implements the same method as documented on socketapi.TCPSock -func (f *tcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) { - nfd, _, err := syscall.Accept(int(f.fd)) - errno := sys.UnwrapOSError(err) - if errno != 0 { - return nil, errno - } - return &tcpConnFile{fd: uintptr(nfd)}, 0 -} - -// Close implements the same method as documented on sys.File -func (f *tcpListenerFile) Close() sys.Errno { - return sys.UnwrapOSError(syscall.Close(int(f.fd))) -} - -// Addr is exposed for testing. -func (f *tcpListenerFile) Addr() *net.TCPAddr { - return f.addr -} - -// SetNonblock implements the same method as documented on fsapi.File -func (f *tcpListenerFile) SetNonblock(enabled bool) sys.Errno { - f.nonblock = enabled - return sys.UnwrapOSError(setNonblock(f.fd, enabled)) -} - -// IsNonblock implements the same method as documented on fsapi.File -func (f *tcpListenerFile) IsNonblock() bool { - return f.nonblock -} - -// Poll implements the same method as documented on fsapi.File -func (f *tcpListenerFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) { - return false, sys.ENOSYS -} - -var _ socketapi.TCPConn = (*tcpConnFile)(nil) - -type tcpConnFile struct { - baseSockFile - - fd uintptr - nonblock bool - - // closed is true when closed was called. This ensures proper sys.EBADF - closed bool -} - -func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { - f, err := tc.File() - if err != nil { - panic(err) - } - return &tcpConnFile{fd: f.Fd()} -} - -// Read implements the same method as documented on sys.File -func (f *tcpConnFile) Read(buf []byte) (n int, errno sys.Errno) { - n, err := syscall.Read(int(f.fd), buf) - if err != nil { - // Defer validation overhead until we've already had an error. - errno = sys.UnwrapOSError(err) - errno = fileError(f, f.closed, errno) - } - return n, errno -} - -// Write implements the same method as documented on sys.File -func (f *tcpConnFile) Write(buf []byte) (n int, errno sys.Errno) { - n, err := syscall.Write(int(f.fd), buf) - if err != nil { - // Defer validation overhead until we've already had an error. - errno = sys.UnwrapOSError(err) - errno = fileError(f, f.closed, errno) - } - return n, errno -} - -// Recvfrom implements the same method as documented on socketapi.TCPConn -func (f *tcpConnFile) Recvfrom(p []byte, flags int) (n int, errno sys.Errno) { - if flags != MSG_PEEK { - errno = sys.EINVAL - return - } - n, _, recvfromErr := syscall.Recvfrom(int(f.fd), p, MSG_PEEK) - errno = sys.UnwrapOSError(recvfromErr) - return n, errno -} - -// Shutdown implements the same method as documented on sys.Conn -func (f *tcpConnFile) Shutdown(how int) sys.Errno { - var err error - switch how { - case syscall.SHUT_RD, syscall.SHUT_WR: - err = syscall.Shutdown(int(f.fd), how) - case syscall.SHUT_RDWR: - return f.close() - default: - return sys.EINVAL - } - return sys.UnwrapOSError(err) -} - -// Close implements the same method as documented on sys.File -func (f *tcpConnFile) Close() sys.Errno { - return f.close() +func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, sys.Errno) { + n, errno := syscallConnControl(conn, func(fd uintptr) (int, sys.Errno) { + if ready, errno := poll(fd, fsapi.POLLIN, 0); !ready || errno != 0 { + return -1, errno + } else { + return 0, errno + } + }) + return n >= 0, errno } -func (f *tcpConnFile) close() sys.Errno { - if f.closed { - return 0 - } - f.closed = true - return sys.UnwrapOSError(syscall.Shutdown(int(f.fd), syscall.SHUT_RDWR)) +func setNonblockSocket(fd uintptr, enabled bool) sys.Errno { + return sys.UnwrapOSError(setNonblock(fd, enabled)) } -// SetNonblock implements the same method as documented on fsapi.File -func (f *tcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) { - f.nonblock = enabled - return sys.UnwrapOSError(setNonblock(f.fd, enabled)) +func readSocket(fd uintptr, buf []byte) (int, sys.Errno) { + n, err := syscall.Read(int(fd), buf) + return n, sys.UnwrapOSError(err) } -// IsNonblock implements the same method as documented on fsapi.File -func (f *tcpConnFile) IsNonblock() bool { - return f.nonblock +func writeSocket(fd uintptr, buf []byte) (int, sys.Errno) { + n, err := syscall.Write(int(fd), buf) + return n, sys.UnwrapOSError(err) } -// Poll implements the same method as documented on fsapi.File -func (f *tcpConnFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno sys.Errno) { - return false, sys.ENOSYS +func recvfrom(fd uintptr, buf []byte, flags int32) (n int, errno sys.Errno) { + n, _, err := syscall.Recvfrom(int(fd), buf, int(flags)) + return n, sys.UnwrapOSError(err) } diff --git a/internal/sysfs/sock_unsupported.go b/internal/sysfs/sock_unsupported.go index 76a10a8b61..cee02742b5 100644 --- a/internal/sysfs/sock_unsupported.go +++ b/internal/sysfs/sock_unsupported.go @@ -4,8 +4,11 @@ package sysfs import ( "net" + "syscall" "github.com/tetratelabs/wazero/experimental/sys" + experimentalsys "github.com/tetratelabs/wazero/experimental/sys" + "github.com/tetratelabs/wazero/internal/fsapi" socketapi "github.com/tetratelabs/wazero/internal/sock" ) @@ -24,3 +27,33 @@ type unsupportedSockFile struct { func (f *unsupportedSockFile) Accept() (socketapi.TCPConn, sys.Errno) { return nil, sys.ENOSYS } + +func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, sys.Errno) { + return false, sys.ENOTSUP +} + +func setNonblockSocket(fd uintptr, enabled bool) sys.Errno { + return sys.ENOTSUP +} + +func readSocket(fd uintptr, buf []byte) (int, sys.Errno) { + return -1, sys.ENOTSUP +} + +func writeSocket(fd uintptr, buf []byte) (int, sys.Errno) { + return -1, sys.ENOTSUP +} + +func recvfrom(fd uintptr, buf []byte, flags int32) (n int, errno sys.Errno) { + return -1, sys.ENOTSUP +} + +// syscallConnControl extracts a syscall.RawConn from the given syscall.Conn and applies +// the given fn to a file descriptor, returning an integer or a nonzero syscall.Errno on failure. +// +// syscallConnControl streamlines the pattern of extracting the syscall.Rawconn, +// invoking its syscall.RawConn.Control method, then handling properly the errors that may occur +// within fn or returned by syscall.RawConn.Control itself. +func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, experimentalsys.Errno)) (n int, errno sys.Errno) { + return -1, sys.ENOTSUP +} diff --git a/internal/sysfs/sock_windows.go b/internal/sysfs/sock_windows.go index ed275e6290..703df42fcc 100644 --- a/internal/sysfs/sock_windows.go +++ b/internal/sysfs/sock_windows.go @@ -29,20 +29,24 @@ var ( procioctlsocket = modws2_32.NewProc("ioctlsocket") ) +func newTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { + return newDefaultTCPListenerFile(tl) +} + // recvfrom exposes the underlying syscall in Windows. // // Note: since we are only using this to expose MSG_PEEK, // we do not need really need all the parameters that are actually // allowed in WinSock. // We ignore `from *sockaddr` and `fromlen *int`. -func recvfrom(s syscall.Handle, buf []byte, flags int32) (n int, errno sys.Errno) { +func recvfrom(s uintptr, buf []byte, flags int32) (n int, errno sys.Errno) { var _p0 *byte if len(buf) > 0 { _p0 = &buf[0] } r0, _, e1 := syscall.SyscallN( procrecvfrom.Addr(), - uintptr(s), + s, uintptr(unsafe.Pointer(_p0)), uintptr(len(buf)), uintptr(flags), @@ -51,7 +55,7 @@ func recvfrom(s syscall.Handle, buf []byte, flags int32) (n int, errno sys.Errno return int(r0), sys.UnwrapOSError(e1) } -func setNonblockSocket(fd syscall.Handle, enabled bool) sys.Errno { +func setNonblockSocket(fd uintptr, enabled bool) sys.Errno { opt := uint64(0) if enabled { opt = 1 @@ -65,26 +69,6 @@ func setNonblockSocket(fd syscall.Handle, enabled bool) sys.Errno { return sys.UnwrapOSError(errno) } -// syscallConnControl extracts a syscall.RawConn from the given syscall.Conn and applies -// the given fn to a file descriptor, returning an integer or a nonzero syscall.Errno on failure. -// -// syscallConnControl streamlines the pattern of extracting the syscall.Rawconn, -// invoking its syscall.RawConn.Control method, then handling properly the errors that may occur -// within fn or returned by syscall.RawConn.Control itself. -func syscallConnControl(conn syscall.Conn, fn func(fd uintptr) (int, sys.Errno)) (n int, errno sys.Errno) { - syscallConn, err := conn.SyscallConn() - if err != nil { - return 0, sys.UnwrapOSError(err) - } - // Prioritize the inner errno over Control - if controlErr := syscallConn.Control(func(fd uintptr) { - n, errno = fn(fd) - }); errno == 0 { - errno = sys.UnwrapOSError(controlErr) - } - return -} - func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, sys.Errno) { if flag != fsapi.POLLIN { return false, sys.ENOTSUP @@ -94,192 +78,3 @@ func _pollSock(conn syscall.Conn, flag fsapi.Pflag, timeoutMillis int32) (bool, }) return n > 0, errno } - -// newTCPListenerFile is a constructor for a socketapi.TCPSock. -// -// Note: currently the Windows implementation of socketapi.TCPSock -// returns a winTcpListenerFile, which is a specialized TCPSock -// that delegates to a .net.TCPListener. -// The current strategy is to delegate most behavior to the Go -// standard library, instead of invoke syscalls/Win32 APIs -// because they are sensibly different from Unix's. -func newTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { - return &winTcpListenerFile{tl: tl} -} - -var _ socketapi.TCPSock = (*winTcpListenerFile)(nil) - -type winTcpListenerFile struct { - baseSockFile - - tl *net.TCPListener - closed bool - nonblock bool -} - -// Accept implements the same method as documented on socketapi.TCPSock -func (f *winTcpListenerFile) Accept() (socketapi.TCPConn, sys.Errno) { - // Ensure we have an incoming connection using winsock_select, otherwise return immediately. - if f.nonblock { - if ready, errno := _pollSock(f.tl, fsapi.POLLIN, 0); !ready || errno != 0 { - return nil, sys.EAGAIN - } - } - - // Accept normally blocks goroutines, but we - // made sure that we have an incoming connection, - // so we should be safe. - if conn, err := f.tl.Accept(); err != nil { - return nil, sys.UnwrapOSError(err) - } else { - return newTcpConn(conn.(*net.TCPConn)), 0 - } -} - -// Close implements the same method as documented on sys.File -func (f *winTcpListenerFile) Close() sys.Errno { - if !f.closed { - return sys.UnwrapOSError(f.tl.Close()) - } - return 0 -} - -// Addr is exposed for testing. -func (f *winTcpListenerFile) Addr() *net.TCPAddr { - return f.tl.Addr().(*net.TCPAddr) -} - -// IsNonblock implements the same method as documented on fsapi.File -func (f *winTcpListenerFile) IsNonblock() bool { - return f.nonblock -} - -// SetNonblock implements the same method as documented on fsapi.File -func (f *winTcpListenerFile) SetNonblock(enabled bool) sys.Errno { - f.nonblock = enabled - _, errno := syscallConnControl(f.tl, func(fd uintptr) (int, sys.Errno) { - return 0, setNonblockSocket(syscall.Handle(fd), enabled) - }) - return errno -} - -// Poll implements the same method as documented on fsapi.File -func (f *winTcpListenerFile) Poll(fsapi.Pflag, int32) (ready bool, errno sys.Errno) { - return false, sys.ENOSYS -} - -var _ socketapi.TCPConn = (*winTcpConnFile)(nil) - -// winTcpConnFile is a blocking connection. -// -// It is a wrapper for an underlying net.TCPConn. -type winTcpConnFile struct { - baseSockFile - - tc *net.TCPConn - - // nonblock is true when the underlying connection is flagged as non-blocking. - // This ensures that reads and writes return sys.EAGAIN without blocking the caller. - nonblock bool - // closed is true when closed was called. This ensures proper sys.EBADF - closed bool -} - -func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { - return &winTcpConnFile{tc: tc} -} - -// Read implements the same method as documented on sys.File -func (f *winTcpConnFile) Read(buf []byte) (n int, errno sys.Errno) { - if len(buf) == 0 { - return 0, 0 // Short-circuit 0-len reads. - } - if nonBlockingFileReadSupported && f.IsNonblock() { - n, errno = syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { - return readSocket(syscall.Handle(fd), buf) - }) - } else { - n, errno = read(f.tc, buf) - } - if errno != 0 { - // Defer validation overhead until we've already had an error. - errno = fileError(f, f.closed, errno) - } - return -} - -// Write implements the same method as documented on sys.File -func (f *winTcpConnFile) Write(buf []byte) (n int, errno sys.Errno) { - if nonBlockingFileWriteSupported && f.IsNonblock() { - return syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { - return writeSocket(fd, buf) - }) - } else { - n, errno = write(f.tc, buf) - } - if errno != 0 { - // Defer validation overhead until we've already had an error. - errno = fileError(f, f.closed, errno) - } - return -} - -// Recvfrom implements the same method as documented on socketapi.TCPConn -func (f *winTcpConnFile) Recvfrom(p []byte, flags int) (n int, errno sys.Errno) { - if flags != MSG_PEEK { - errno = sys.EINVAL - return - } - return syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { - return recvfrom(syscall.Handle(fd), p, MSG_PEEK) - }) -} - -// Shutdown implements the same method as documented on sys.Conn -func (f *winTcpConnFile) Shutdown(how int) sys.Errno { - // FIXME: can userland shutdown listeners? - var err error - switch how { - case syscall.SHUT_RD: - err = f.tc.CloseRead() - case syscall.SHUT_WR: - err = f.tc.CloseWrite() - case syscall.SHUT_RDWR: - return f.close() - default: - return sys.EINVAL - } - return sys.UnwrapOSError(err) -} - -// Close implements the same method as documented on sys.File -func (f *winTcpConnFile) Close() sys.Errno { - return f.close() -} - -func (f *winTcpConnFile) close() sys.Errno { - if f.closed { - return 0 - } - f.closed = true - return f.Shutdown(syscall.SHUT_RDWR) -} - -// IsNonblock implements the same method as documented on fsapi.File -func (f *winTcpConnFile) IsNonblock() bool { - return f.nonblock -} - -// SetNonblock implements the same method as documented on fsapi.File -func (f *winTcpConnFile) SetNonblock(enabled bool) (errno sys.Errno) { - f.nonblock = true - _, errno = syscallConnControl(f.tc, func(fd uintptr) (int, sys.Errno) { - return 0, sys.UnwrapOSError(setNonblockSocket(syscall.Handle(fd), enabled)) - }) - return -} - -// Poll implements the same method as documented on fsapi.File -func (f *winTcpConnFile) Poll(fsapi.Pflag, int32) (ready bool, errno sys.Errno) { - return false, sys.ENOSYS -}