package io import ( "errors" "io" "net" "sync" "sync/atomic" "time" "git.smesh.lol/torly/pkg/unix" ) // fdConn is a non-blocking socket connection owned by one reader and one // writer domain. The reader and writer only share the fd capability; each uses // its own direction and its own deadline field. type fdConn struct { fd int remote fdAddr readDeadline time.Time writeDeadline time.Time closeOnce sync.Once closed atomic.Bool } type fdAddr string func (a fdAddr) Network() string { return "tcp" } func (a fdAddr) String() string { return string(a) } type timeoutError struct{} func (timeoutError) Error() string { return "i/o timeout" } func (timeoutError) Timeout() bool { return true } func (timeoutError) Temporary() bool { return true } var errFDClosed = errors.New("io: file already closed") func newFDConn(fd int, remote string) *fdConn { return &fdConn{fd: fd, remote: fdAddr(remote)} } func (c *fdConn) Read(p []byte) (int, error) { for { if c.isClosed() { return 0, errFDClosed } n, err := unix.Read(c.fd, p) if err == nil { if n == 0 { return 0, io.EOF } return n, nil } if err == unix.EINTR { continue } if err == unix.EAGAIN { if werr := waitFD(c.fd, unix.POLLIN, c.readDeadline); werr != nil { return 0, werr } continue } return 0, err } } func (c *fdConn) Write(p []byte) (int, error) { written := 0 for written < len(p) { if c.isClosed() { return written, errFDClosed } n, err := unix.Write(c.fd, p[written:]) if err == nil { written += n continue } if err == unix.EINTR { continue } if err == unix.EAGAIN { if werr := waitFD(c.fd, unix.POLLOUT, c.writeDeadline); werr != nil { return written, werr } continue } return written, err } return written, nil } func (c *fdConn) Close() error { var err error c.closeOnce.Do(func() { c.closed.Store(true) unix.Shutdown(c.fd, unix.SHUT_RDWR) err = unix.Close(c.fd) }) return err } func (c *fdConn) LocalAddr() net.Addr { return fdAddr("") } func (c *fdConn) RemoteAddr() net.Addr { return c.remote } func (c *fdConn) SetDeadline(t time.Time) error { c.readDeadline = t c.writeDeadline = t return nil } func (c *fdConn) SetReadDeadline(t time.Time) error { c.readDeadline = t; return nil } func (c *fdConn) SetWriteDeadline(t time.Time) error { c.writeDeadline = t; return nil } func (c *fdConn) isClosed() bool { return c.closed.Load() } // waitFD waits for an event on fd, waking at least every 200 ms so a shut-down // breaker in another domain is noticed even without a socket event. func waitFD(fd int, events int16, deadline time.Time) error { for { timeout := 200 if !deadline.IsZero() { left := time.Until(deadline) if left <= 0 { return timeoutError{} } ms := int(left / time.Millisecond) if ms < timeout { timeout = ms } } if timeout < 0 { timeout = 0 } pfd := []unix.PollFd{{Fd: int32(fd), Events: events}} n, err := unix.Poll(pfd, timeout) if err != nil { if err == unix.EINTR { continue } return err } if n > 0 { return nil } if !deadline.IsZero() && time.Now().After(deadline) { return timeoutError{} } } }