github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/sysfs/sock.go (about) 1 package sysfs 2 3 import ( 4 "net" 5 "os" 6 7 experimentalsys "github.com/bananabytelabs/wazero/experimental/sys" 8 "github.com/bananabytelabs/wazero/internal/fsapi" 9 socketapi "github.com/bananabytelabs/wazero/internal/sock" 10 "github.com/bananabytelabs/wazero/sys" 11 ) 12 13 // NewTCPListenerFile creates a socketapi.TCPSock for a given *net.TCPListener. 14 func 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. 20 type baseSockFile struct { 21 experimentalsys.UnimplementedFile 22 } 23 24 var _ experimentalsys.File = (*baseSockFile)(nil) 25 26 // IsDir implements the same method as documented on File.IsDir 27 func (*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 34 func (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 40 var _ socketapi.TCPSock = (*tcpListenerFile)(nil) 41 42 type 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. 57 func newDefaultTCPListenerFile(tl *net.TCPListener) socketapi.TCPSock { 58 return &tcpListenerFile{tl: tl} 59 } 60 61 // Accept implements the same method as documented on socketapi.TCPSock 62 func (f *tcpListenerFile) Accept() (socketapi.TCPConn, experimentalsys.Errno) { 63 // Ensure we have an incoming connection, otherwise return immediately. 64 if f.nonblock { 65 if ready, errno := _pollSock(f.tl, fsapi.POLLIN, 0); !ready || errno != 0 { 66 return nil, experimentalsys.EAGAIN 67 } 68 } 69 70 // Accept normally blocks goroutines, but we 71 // made sure that we have an incoming connection, 72 // so we should be safe. 73 if conn, err := f.tl.Accept(); err != nil { 74 return nil, experimentalsys.UnwrapOSError(err) 75 } else { 76 return newTcpConn(conn.(*net.TCPConn)), 0 77 } 78 } 79 80 // Close implements the same method as documented on experimentalsys.File 81 func (f *tcpListenerFile) Close() experimentalsys.Errno { 82 if !f.closed { 83 return experimentalsys.UnwrapOSError(f.tl.Close()) 84 } 85 return 0 86 } 87 88 // Addr is exposed for testing. 89 func (f *tcpListenerFile) Addr() *net.TCPAddr { 90 return f.tl.Addr().(*net.TCPAddr) 91 } 92 93 // SetNonblock implements the same method as documented on fsapi.File 94 func (f *tcpListenerFile) SetNonblock(enabled bool) (errno experimentalsys.Errno) { 95 f.nonblock = enabled 96 _, errno = syscallConnControl(f.tl, func(fd uintptr) (int, experimentalsys.Errno) { 97 return 0, setNonblockSocket(fd, enabled) 98 }) 99 return 100 } 101 102 // IsNonblock implements the same method as documented on fsapi.File 103 func (f *tcpListenerFile) IsNonblock() bool { 104 return f.nonblock 105 } 106 107 // Poll implements the same method as documented on fsapi.File 108 func (f *tcpListenerFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { 109 return false, experimentalsys.ENOSYS 110 } 111 112 var _ socketapi.TCPConn = (*tcpConnFile)(nil) 113 114 type tcpConnFile struct { 115 baseSockFile 116 117 tc *net.TCPConn 118 119 // nonblock is true when the underlying connection is flagged as non-blocking. 120 // This ensures that reads and writes return experimentalsys.EAGAIN without blocking the caller. 121 nonblock bool 122 // closed is true when closed was called. This ensures proper experimentalsys.EBADF 123 closed bool 124 } 125 126 func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { 127 return &tcpConnFile{tc: tc} 128 } 129 130 // Read implements the same method as documented on experimentalsys.File 131 func (f *tcpConnFile) Read(buf []byte) (n int, errno experimentalsys.Errno) { 132 if len(buf) == 0 { 133 return 0, 0 // Short-circuit 0-len reads. 134 } 135 if nonBlockingFileReadSupported && f.IsNonblock() { 136 n, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { 137 n, err := readSocket(fd, buf) 138 errno = experimentalsys.UnwrapOSError(err) 139 errno = fileError(f, f.closed, errno) 140 return n, errno 141 }) 142 } else { 143 n, errno = read(f.tc, buf) 144 } 145 if errno != 0 { 146 // Defer validation overhead until we've already had an error. 147 errno = fileError(f, f.closed, errno) 148 } 149 return 150 } 151 152 // Write implements the same method as documented on experimentalsys.File 153 func (f *tcpConnFile) Write(buf []byte) (n int, errno experimentalsys.Errno) { 154 if nonBlockingFileWriteSupported && f.IsNonblock() { 155 return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { 156 n, err := writeSocket(fd, buf) 157 errno = experimentalsys.UnwrapOSError(err) 158 errno = fileError(f, f.closed, errno) 159 return n, errno 160 }) 161 } else { 162 n, errno = write(f.tc, buf) 163 } 164 if errno != 0 { 165 // Defer validation overhead until we've already had an error. 166 errno = fileError(f, f.closed, errno) 167 } 168 return 169 } 170 171 // Recvfrom implements the same method as documented on socketapi.TCPConn 172 func (f *tcpConnFile) Recvfrom(p []byte, flags int) (n int, errno experimentalsys.Errno) { 173 if flags != MSG_PEEK { 174 errno = experimentalsys.EINVAL 175 return 176 } 177 return syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { 178 n, err := recvfrom(fd, p, MSG_PEEK) 179 errno = experimentalsys.UnwrapOSError(err) 180 errno = fileError(f, f.closed, errno) 181 return n, errno 182 }) 183 } 184 185 // Shutdown implements the same method as documented on experimentalsys.Conn 186 func (f *tcpConnFile) Shutdown(how int) experimentalsys.Errno { 187 // FIXME: can userland shutdown listeners? 188 var err error 189 switch how { 190 case socketapi.SHUT_RD: 191 err = f.tc.CloseRead() 192 case socketapi.SHUT_WR: 193 err = f.tc.CloseWrite() 194 case socketapi.SHUT_RDWR: 195 return f.close() 196 default: 197 return experimentalsys.EINVAL 198 } 199 return experimentalsys.UnwrapOSError(err) 200 } 201 202 // Close implements the same method as documented on experimentalsys.File 203 func (f *tcpConnFile) Close() experimentalsys.Errno { 204 return f.close() 205 } 206 207 func (f *tcpConnFile) close() experimentalsys.Errno { 208 if f.closed { 209 return 0 210 } 211 f.closed = true 212 return f.Shutdown(socketapi.SHUT_RDWR) 213 } 214 215 // SetNonblock implements the same method as documented on fsapi.File 216 func (f *tcpConnFile) SetNonblock(enabled bool) (errno experimentalsys.Errno) { 217 f.nonblock = enabled 218 _, errno = syscallConnControl(f.tc, func(fd uintptr) (int, experimentalsys.Errno) { 219 return 0, experimentalsys.UnwrapOSError(setNonblockSocket(fd, enabled)) 220 }) 221 return 222 } 223 224 // IsNonblock implements the same method as documented on fsapi.File 225 func (f *tcpConnFile) IsNonblock() bool { 226 return f.nonblock 227 } 228 229 // Poll implements the same method as documented on fsapi.File 230 func (f *tcpConnFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { 231 return false, experimentalsys.ENOSYS 232 }