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  }