summaryrefslogtreecommitdiffstats
path: root/src/net/fd_unix.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/fd_unix.go')
-rw-r--r--src/net/fd_unix.go203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/net/fd_unix.go b/src/net/fd_unix.go
new file mode 100644
index 0000000..a400c60
--- /dev/null
+++ b/src/net/fd_unix.go
@@ -0,0 +1,203 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build unix
+
+package net
+
+import (
+ "context"
+ "internal/poll"
+ "os"
+ "runtime"
+ "syscall"
+)
+
+const (
+ readSyscallName = "read"
+ readFromSyscallName = "recvfrom"
+ readMsgSyscallName = "recvmsg"
+ writeSyscallName = "write"
+ writeToSyscallName = "sendto"
+ writeMsgSyscallName = "sendmsg"
+)
+
+func newFD(sysfd, family, sotype int, net string) (*netFD, error) {
+ ret := &netFD{
+ pfd: poll.FD{
+ Sysfd: sysfd,
+ IsStream: sotype == syscall.SOCK_STREAM,
+ ZeroReadIsEOF: sotype != syscall.SOCK_DGRAM && sotype != syscall.SOCK_RAW,
+ },
+ family: family,
+ sotype: sotype,
+ net: net,
+ }
+ return ret, nil
+}
+
+func (fd *netFD) init() error {
+ return fd.pfd.Init(fd.net, true)
+}
+
+func (fd *netFD) name() string {
+ var ls, rs string
+ if fd.laddr != nil {
+ ls = fd.laddr.String()
+ }
+ if fd.raddr != nil {
+ rs = fd.raddr.String()
+ }
+ return fd.net + ":" + ls + "->" + rs
+}
+
+func (fd *netFD) connect(ctx context.Context, la, ra syscall.Sockaddr) (rsa syscall.Sockaddr, ret error) {
+ // Do not need to call fd.writeLock here,
+ // because fd is not yet accessible to user,
+ // so no concurrent operations are possible.
+ switch err := connectFunc(fd.pfd.Sysfd, ra); err {
+ case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
+ case nil, syscall.EISCONN:
+ select {
+ case <-ctx.Done():
+ return nil, mapErr(ctx.Err())
+ default:
+ }
+ if err := fd.pfd.Init(fd.net, true); err != nil {
+ return nil, err
+ }
+ runtime.KeepAlive(fd)
+ return nil, nil
+ case syscall.EINVAL:
+ // On Solaris and illumos we can see EINVAL if the socket has
+ // already been accepted and closed by the server. Treat this
+ // as a successful connection--writes to the socket will see
+ // EOF. For details and a test case in C see
+ // https://golang.org/issue/6828.
+ if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" {
+ return nil, nil
+ }
+ fallthrough
+ default:
+ return nil, os.NewSyscallError("connect", err)
+ }
+ if err := fd.pfd.Init(fd.net, true); err != nil {
+ return nil, err
+ }
+ if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
+ fd.pfd.SetWriteDeadline(deadline)
+ defer fd.pfd.SetWriteDeadline(noDeadline)
+ }
+
+ // Start the "interrupter" goroutine, if this context might be canceled.
+ //
+ // The interrupter goroutine waits for the context to be done and
+ // interrupts the dial (by altering the fd's write deadline, which
+ // wakes up waitWrite).
+ ctxDone := ctx.Done()
+ if ctxDone != nil {
+ // Wait for the interrupter goroutine to exit before returning
+ // from connect.
+ done := make(chan struct{})
+ interruptRes := make(chan error)
+ defer func() {
+ close(done)
+ if ctxErr := <-interruptRes; ctxErr != nil && ret == nil {
+ // The interrupter goroutine called SetWriteDeadline,
+ // but the connect code below had returned from
+ // waitWrite already and did a successful connect (ret
+ // == nil). Because we've now poisoned the connection
+ // by making it unwritable, don't return a successful
+ // dial. This was issue 16523.
+ ret = mapErr(ctxErr)
+ fd.Close() // prevent a leak
+ }
+ }()
+ go func() {
+ select {
+ case <-ctxDone:
+ // Force the runtime's poller to immediately give up
+ // waiting for writability, unblocking waitWrite
+ // below.
+ fd.pfd.SetWriteDeadline(aLongTimeAgo)
+ testHookCanceledDial()
+ interruptRes <- ctx.Err()
+ case <-done:
+ interruptRes <- nil
+ }
+ }()
+ }
+
+ for {
+ // Performing multiple connect system calls on a
+ // non-blocking socket under Unix variants does not
+ // necessarily result in earlier errors being
+ // returned. Instead, once runtime-integrated network
+ // poller tells us that the socket is ready, get the
+ // SO_ERROR socket option to see if the connection
+ // succeeded or failed. See issue 7474 for further
+ // details.
+ if err := fd.pfd.WaitWrite(); err != nil {
+ select {
+ case <-ctxDone:
+ return nil, mapErr(ctx.Err())
+ default:
+ }
+ return nil, err
+ }
+ nerr, err := getsockoptIntFunc(fd.pfd.Sysfd, syscall.SOL_SOCKET, syscall.SO_ERROR)
+ if err != nil {
+ return nil, os.NewSyscallError("getsockopt", err)
+ }
+ switch err := syscall.Errno(nerr); err {
+ case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR:
+ case syscall.EISCONN:
+ return nil, nil
+ case syscall.Errno(0):
+ // The runtime poller can wake us up spuriously;
+ // see issues 14548 and 19289. Check that we are
+ // really connected; if not, wait again.
+ if rsa, err := syscall.Getpeername(fd.pfd.Sysfd); err == nil {
+ return rsa, nil
+ }
+ default:
+ return nil, os.NewSyscallError("connect", err)
+ }
+ runtime.KeepAlive(fd)
+ }
+}
+
+func (fd *netFD) accept() (netfd *netFD, err error) {
+ d, rsa, errcall, err := fd.pfd.Accept()
+ if err != nil {
+ if errcall != "" {
+ err = wrapSyscallError(errcall, err)
+ }
+ return nil, err
+ }
+
+ if netfd, err = newFD(d, fd.family, fd.sotype, fd.net); err != nil {
+ poll.CloseFunc(d)
+ return nil, err
+ }
+ if err = netfd.init(); err != nil {
+ netfd.Close()
+ return nil, err
+ }
+ lsa, _ := syscall.Getsockname(netfd.pfd.Sysfd)
+ netfd.setAddr(netfd.addrFunc()(lsa), netfd.addrFunc()(rsa))
+ return netfd, nil
+}
+
+func (fd *netFD) dup() (f *os.File, err error) {
+ ns, call, err := fd.pfd.Dup()
+ if err != nil {
+ if call != "" {
+ err = os.NewSyscallError(call, err)
+ }
+ return nil, err
+ }
+
+ return os.NewFile(uintptr(ns), fd.name()), nil
+}