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  }