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}