sock.go

  1package sysfs
  2
  3import (
  4	"net"
  5	"os"
  6
  7	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
  8	"github.com/tetratelabs/wazero/internal/fsapi"
  9	socketapi "github.com/tetratelabs/wazero/internal/sock"
 10	"github.com/tetratelabs/wazero/sys"
 11)
 12
 13// NewTCPListenerFile creates a socketapi.TCPSock for a given *net.TCPListener.
 14func NewTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock {
 15	return newTCPListenerFile(tl)
 16}
 17
 18// baseSockFile implements base behavior for all TCPSock, TCPConn files,
 19// regardless the platform.
 20type baseSockFile struct {
 21	experimentalsys.UnimplementedFile
 22}
 23
 24var _ experimentalsys.File = (*baseSockFile)(nil)
 25
 26// IsDir implements the same method as documented on File.IsDir
 27func (*baseSockFile) IsDir() (bool, experimentalsys.Errno) {
 28	// We need to override this method because WASI-libc prestats the FD
 29	// and the default impl returns ENOSYS otherwise.
 30	return false, 0
 31}
 32
 33// Stat implements the same method as documented on File.Stat
 34func (f *baseSockFile) Stat() (fs sys.Stat_t, errno experimentalsys.Errno) {
 35	// The mode is not really important, but it should be neither a regular file nor a directory.
 36	fs.Mode = os.ModeIrregular
 37	return
 38}
 39
 40var _ socketapi.TCPSock = (*tcpListenerFile)(nil)
 41
 42type tcpListenerFile struct {
 43	baseSockFile
 44
 45	tl       *net.TCPListener
 46	closed   bool
 47	nonblock bool
 48}
 49
 50// newTCPListenerFile is a constructor for a socketapi.TCPSock.
 51//
 52// The current strategy is to wrap a net.TCPListener
 53// and invoking raw syscalls using syscallConnControl:
 54// this internal calls RawConn.Control(func(fd)), making sure
 55// that the underlying file descriptor is valid throughout
 56// the duration of the syscall.
 57func newDefaultTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock {
 58	return &tcpListenerFile{tl: tl}
 59}
 60
 61// Close implements the same method as documented on experimentalsys.File
 62func (f *tcpListenerFile) Close() experimentalsys.Errno {
 63	if !f.closed {
 64		return experimentalsys.UnwrapOSError(f.tl.Close())
 65	}
 66	return 0
 67}
 68
 69// Addr is exposed for testing.
 70func (f *tcpListenerFile) Addr() *net.TCPAddr {
 71	return f.tl.Addr().(*net.TCPAddr)
 72}
 73
 74// IsNonblock implements the same method as documented on fsapi.File
 75func (f *tcpListenerFile) IsNonblock() bool {
 76	return f.nonblock
 77}
 78
 79// Poll implements the same method as documented on fsapi.File
 80func (f *tcpListenerFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
 81	return false, experimentalsys.ENOSYS
 82}
 83
 84var _ socketapi.TCPConn = (*tcpConnFile)(nil)
 85
 86type tcpConnFile struct {
 87	baseSockFile
 88
 89	tc *net.TCPConn
 90
 91	// nonblock is true when the underlying connection is flagged as non-blocking.
 92	// This ensures that reads and writes return experimentalsys.EAGAIN without blocking the caller.
 93	nonblock bool
 94	// closed is true when closed was called. This ensures proper experimentalsys.EBADF
 95	closed bool
 96}
 97
 98func newTcpConn(tc *net.TCPConn) socketapi.TCPConn {
 99	return &tcpConnFile{tc: tc}
100}
101
102// Read implements the same method as documented on experimentalsys.File
103func (f *tcpConnFile) Read(buf []byte) (n int, errno experimentalsys.Errno) {
104	if len(buf) == 0 {
105		return 0, 0 // Short-circuit 0-len reads.
106	}
107	if nonBlockingFileReadSupported && f.IsNonblock() {
108		n, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) {
109			n, err := readSocket(fd, buf)
110			errno = experimentalsys.UnwrapOSError(err)
111			errno = fileError(f, f.closed, errno)
112			return n, errno
113		})
114	} else {
115		n, errno = read(f.tc, buf)
116	}
117	if errno != 0 {
118		// Defer validation overhead until we've already had an error.
119		errno = fileError(f, f.closed, errno)
120	}
121	return
122}
123
124// Write implements the same method as documented on experimentalsys.File
125func (f *tcpConnFile) Write(buf []byte) (n int, errno experimentalsys.Errno) {
126	if nonBlockingFileWriteSupported && f.IsNonblock() {
127		return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) {
128			n, err := writeSocket(fd, buf)
129			errno = experimentalsys.UnwrapOSError(err)
130			errno = fileError(f, f.closed, errno)
131			return n, errno
132		})
133	} else {
134		n, errno = write(f.tc, buf)
135	}
136	if errno != 0 {
137		// Defer validation overhead until we've already had an error.
138		errno = fileError(f, f.closed, errno)
139	}
140	return
141}
142
143// Recvfrom implements the same method as documented on socketapi.TCPConn
144func (f *tcpConnFile) Recvfrom(p []byte, flags int) (n int, errno experimentalsys.Errno) {
145	if flags != MSG_PEEK {
146		errno = experimentalsys.EINVAL
147		return
148	}
149	return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) {
150		n, err := recvfrom(fd, p, MSG_PEEK)
151		errno = experimentalsys.UnwrapOSError(err)
152		errno = fileError(f, f.closed, errno)
153		return n, errno
154	})
155}
156
157// Close implements the same method as documented on experimentalsys.File
158func (f *tcpConnFile) Close() experimentalsys.Errno {
159	return f.close()
160}
161
162func (f *tcpConnFile) close() experimentalsys.Errno {
163	if f.closed {
164		return 0
165	}
166	f.closed = true
167	return f.Shutdown(socketapi.SHUT_RDWR)
168}
169
170// SetNonblock implements the same method as documented on fsapi.File
171func (f *tcpConnFile) SetNonblock(enabled bool) (errno experimentalsys.Errno) {
172	f.nonblock = enabled
173	_, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) {
174		return 0, experimentalsys.UnwrapOSError(setNonblockSocket(fd, enabled))
175	})
176	return
177}
178
179// IsNonblock implements the same method as documented on fsapi.File
180func (f *tcpConnFile) IsNonblock() bool {
181	return f.nonblock
182}
183
184// Poll implements the same method as documented on fsapi.File
185func (f *tcpConnFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) {
186	return false, experimentalsys.ENOSYS
187}