diff options
Diffstat (limited to 'src/net/fd_unix.go')
-rw-r--r-- | src/net/fd_unix.go | 203 |
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..1c9bba3 --- /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. + +// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris + +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 background context cannot) + // + // 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). + if ctx != context.Background() { + // 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 <-ctx.Done(): + // 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 <-ctx.Done(): + 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 +} |