github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/sysfs/sock.go (about) 1 package sysfs 2 3 import ( 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. 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 // Close implements the same method as documented on experimentalsys.File 62 func (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. 70 func (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 75 func (f *tcpListenerFile) IsNonblock() bool { 76 return f.nonblock 77 } 78 79 // Poll implements the same method as documented on fsapi.File 80 func (f *tcpListenerFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { 81 return false, experimentalsys.ENOSYS 82 } 83 84 var _ socketapi.TCPConn = (*tcpConnFile)(nil) 85 86 type 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 98 func newTcpConn(tc *net.TCPConn) socketapi.TCPConn { 99 return &tcpConnFile{tc: tc} 100 } 101 102 // Read implements the same method as documented on experimentalsys.File 103 func (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 125 func (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 144 func (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 158 func (f *tcpConnFile) Close() experimentalsys.Errno { 159 return f.close() 160 } 161 162 func (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 171 func (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 180 func (f *tcpConnFile) IsNonblock() bool { 181 return f.nonblock 182 } 183 184 // Poll implements the same method as documented on fsapi.File 185 func (f *tcpConnFile) Poll(flag fsapi.Pflag, timeoutMillis int32) (ready bool, errno experimentalsys.Errno) { 186 return false, experimentalsys.ENOSYS 187 }