Along with the removal of the slow path from Linux and *BSD.
For #59359
Change-Id: I6c79594252e5e5f1c1c57c11e09458fcae3793d2
Reviewed-on: https://go-review.googlesource.com/c/go/+/577175
Reviewed-by: Damien Neil <dneil@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Andy Pan <panjf2000@gmail.com>
// This file implements accept for platforms that provide a fast path for
// setting SetNonblock and CloseOnExec.
-//go:build dragonfly || freebsd || (linux && !arm) || netbsd || openbsd || solaris
+//go:build dragonfly || freebsd || (linux && !arm) || netbsd || openbsd
package poll
// descriptor as nonblocking and close-on-exec.
func accept(s int) (int, syscall.Sockaddr, string, error) {
ns, sa, err := Accept4Func(s, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
- // TODO: We can remove the fallback on Linux and *BSD,
- // as currently supported versions all support accept4
- // with SOCK_CLOEXEC, but Solaris does not. See issue #59359.
- switch err {
- case nil:
- return ns, sa, "", nil
- default: // errors other than the ones listed
- return -1, sa, "accept4", err
- case syscall.ENOSYS: // syscall missing
- case syscall.EINVAL: // some Linux use this instead of ENOSYS
- case syscall.EACCES: // some Linux use this instead of ENOSYS
- case syscall.EFAULT: // some Linux use this instead of ENOSYS
- }
-
- // See ../syscall/exec_unix.go for description of ForkLock.
- // It is probably okay to hold the lock across syscall.Accept
- // because we have put fd.sysfd into non-blocking mode.
- // However, a call to the File method will put it back into
- // blocking mode. We can't take that risk, so no use of ForkLock here.
- ns, sa, err = AcceptFunc(s)
- if err == nil {
- syscall.CloseOnExec(ns)
- }
if err != nil {
- return -1, nil, "accept", err
- }
- if err = syscall.SetNonblock(ns, true); err != nil {
- CloseFunc(ns)
- return -1, nil, "setnonblock", err
+ return -1, nil, "accept4", err
}
return ns, sa, "", nil
}
--- /dev/null
+// Copyright 2024 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.
+
+// This file implements accept for platforms that provide a fast path for
+// setting SetNonblock and CloseOnExec, but don't necessarily have accept4.
+// The accept4(3c) function was added to Oracle Solaris in the Solaris 11.4.0
+// release. Thus, on releases prior to 11.4, we fall back to the combination
+// of accept(3c) and fcntl(2).
+
+package poll
+
+import (
+ "internal/syscall/unix"
+ "syscall"
+)
+
+// Wrapper around the accept system call that marks the returned file
+// descriptor as nonblocking and close-on-exec.
+func accept(s int) (int, syscall.Sockaddr, string, error) {
+ // Perform a cheap test and try the fast path first.
+ if unix.SupportAccept4() {
+ ns, sa, err := Accept4Func(s, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
+ if err != nil {
+ return -1, nil, "accept4", err
+ }
+ return ns, sa, "", nil
+ }
+
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ // It is probably okay to hold the lock across syscall.Accept
+ // because we have put fd.sysfd into non-blocking mode.
+ // However, a call to the File method will put it back into
+ // blocking mode. We can't take that risk, so no use of ForkLock here.
+ ns, sa, err := AcceptFunc(s)
+ if err == nil {
+ syscall.CloseOnExec(ns)
+ }
+ if err != nil {
+ return -1, nil, "accept", err
+ }
+ if err = syscall.SetNonblock(ns, true); err != nil {
+ CloseFunc(ns)
+ return -1, nil, "setnonblock", err
+ }
+ return ns, sa, "", nil
+}
TEXT ·syscall6(SB),NOSPLIT,$0-88
JMP syscall·sysvicall6(SB)
+
+TEXT ·rawSyscall6(SB),NOSPLIT,$0-88
+ JMP syscall·rawSysvicall6(SB)
// Implemented as sysvicall6 in runtime/syscall_solaris.go.
func syscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
+// Implemented as rawsysvicall6 in runtime/syscall_solaris.go.
+func rawSyscall6(trap, nargs, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err syscall.Errno)
+
//go:cgo_import_dynamic libc_fstatat fstatat "libc.so"
//go:cgo_import_dynamic libc_openat openat "libc.so"
//go:cgo_import_dynamic libc_unlinkat unlinkat "libc.so"
+//go:cgo_import_dynamic libc_uname uname "libc.so"
const (
AT_REMOVEDIR = 0x1
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-//go:build !linux
+//go:build !linux && !solaris
package unix
--- /dev/null
+// Copyright 2024 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.
+
+package unix
+
+import (
+ "runtime"
+ "sync"
+ "syscall"
+ "unsafe"
+)
+
+//go:linkname procUname libc_uname
+
+var procUname uintptr
+
+// utsname represents the fields of a struct utsname defined in <sys/utsname.h>.
+type utsname struct {
+ Sysname [257]byte
+ Nodename [257]byte
+ Release [257]byte
+ Version [257]byte
+ Machine [257]byte
+}
+
+// KernelVersion returns major and minor kernel version numbers
+// parsed from the syscall.Uname's Version field, or (0, 0) if the
+// version can't be obtained or parsed.
+func KernelVersion() (major int, minor int) {
+ var un utsname
+ _, _, errno := rawSyscall6(uintptr(unsafe.Pointer(&procUname)), 1, uintptr(unsafe.Pointer(&un)), 0, 0, 0, 0, 0)
+ if errno != 0 {
+ return 0, 0
+ }
+
+ // The version string is in the form "<version>.<update>.<sru>.<build>.<reserved>"
+ // on Solaris: https://blogs.oracle.com/solaris/post/whats-in-a-uname-
+ // Therefore, we use the Version field on Solaris when available.
+ ver := un.Version[:]
+ if runtime.GOOS == "illumos" {
+ // Illumos distributions use different formats without a parsable
+ // and unified pattern for the Version field while Release level
+ // string is guaranteed to be in x.y or x.y.z format regardless of
+ // whether the kernel is Solaris or illumos.
+ ver = un.Release[:]
+ }
+
+ parseNext := func() (n int) {
+ for i, c := range ver {
+ if c == '.' {
+ ver = ver[i+1:]
+ return
+ }
+ if '0' <= c && c <= '9' {
+ n = n*10 + int(c-'0')
+ }
+ }
+ ver = nil
+ return
+ }
+
+ major = parseNext()
+ minor = parseNext()
+
+ return
+}
+
+// SupportSockNonblockCloexec tests if SOCK_NONBLOCK and SOCK_CLOEXEC are supported
+// for socket() system call, returns true if affirmative.
+var SupportSockNonblockCloexec = sync.OnceValue(func() bool {
+ // First test if socket() supports SOCK_NONBLOCK and SOCK_CLOEXEC directly.
+ s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
+ if err == nil {
+ syscall.Close(s)
+ return true
+ }
+ if err != syscall.EPROTONOSUPPORT && err != syscall.EINVAL {
+ // Something wrong with socket(), fall back to checking the kernel version.
+ major, minor := KernelVersion()
+ if runtime.GOOS == "illumos" {
+ return major > 5 || (major == 5 && minor >= 11) // minimal requirement is SunOS 5.11
+ }
+ return major > 11 || (major == 11 && minor >= 4)
+ }
+ return false
+})
+
+// SupportAccept4 tests whether accept4 system call is available.
+var SupportAccept4 = sync.OnceValue(func() bool {
+ for {
+ // Test if the accept4() is available.
+ _, _, err := syscall.Accept4(0, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
+ if err == syscall.EINTR {
+ continue
+ }
+ return err != syscall.ENOSYS
+ }
+})
--- /dev/null
+// Copyright 2024 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 solaris
+
+package unix_test
+
+import (
+ "internal/syscall/unix"
+ "runtime"
+ "syscall"
+ "testing"
+)
+
+func TestSupportSockNonblockCloexec(t *testing.T) {
+ // Test that SupportSockNonblockCloexec returns true if socket succeeds with SOCK_NONBLOCK and SOCK_CLOEXEC.
+ s, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, 0)
+ if err == nil {
+ syscall.Close(s)
+ }
+ wantSock := err != syscall.EPROTONOSUPPORT && err != syscall.EINVAL
+ gotSock := unix.SupportSockNonblockCloexec()
+ if wantSock != gotSock {
+ t.Fatalf("SupportSockNonblockCloexec, got %t; want %t", gotSock, wantSock)
+ }
+
+ // Test that SupportAccept4 returns true if accept4 is available.
+ for {
+ _, _, err = syscall.Accept4(0, syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC)
+ if err != syscall.EINTR {
+ break
+ }
+ }
+ wantAccept4 := err != syscall.ENOSYS
+ gotAccept4 := unix.SupportAccept4()
+ if wantAccept4 != gotAccept4 {
+ t.Fatalf("SupportAccept4, got %t; want %t", gotAccept4, wantAccept4)
+ }
+
+ // Test that the version returned by KernelVersion matches expectations.
+ major, minor := unix.KernelVersion()
+ t.Logf("Kernel version: %d.%d", major, minor)
+ if runtime.GOOS == "illumos" {
+ if gotSock && gotAccept4 && (major < 5 || (major == 5 && minor < 11)) {
+ t.Fatalf("SupportSockNonblockCloexec and SupportAccept4 are true, but kernel version is older than 5.11, SunOS version: %d.%d", major, minor)
+ }
+ if !gotSock && !gotAccept4 && (major > 5 || (major == 5 && minor >= 11)) {
+ t.Errorf("SupportSockNonblockCloexec and SupportAccept4 are false, but kernel version is 5.11 or newer, SunOS version: %d.%d", major, minor)
+ }
+ } else { // Solaris
+ if gotSock && gotAccept4 && (major < 11 || (major == 11 && minor < 4)) {
+ t.Fatalf("SupportSockNonblockCloexec and SupportAccept4 are true, but kernel version is older than 11.4, Solaris version: %d.%d", major, minor)
+ }
+ if !gotSock && !gotAccept4 && (major > 11 || (major == 11 && minor >= 4)) {
+ t.Errorf("SupportSockNonblockCloexec and SupportAccept4 are false, but kernel version is 11.4 or newer, Solaris version: %d.%d", major, minor)
+ }
+ }
+}
// This file implements sysSocket for platforms that provide a fast path for
// setting SetNonblock and CloseOnExec.
-//go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
+//go:build dragonfly || freebsd || linux || netbsd || openbsd
package net
import (
- "internal/poll"
"os"
"syscall"
)
// descriptor as nonblocking and close-on-exec.
func sysSocket(family, sotype, proto int) (int, error) {
s, err := socketFunc(family, sotype|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, proto)
- // TODO: We can remove the fallback on Linux and *BSD,
- // as currently supported versions all support accept4
- // with SOCK_CLOEXEC, but Solaris does not. See issue #59359.
- switch err {
- case nil:
- return s, nil
- default:
- return -1, os.NewSyscallError("socket", err)
- case syscall.EPROTONOSUPPORT, syscall.EINVAL:
- }
-
- // See ../syscall/exec_unix.go for description of ForkLock.
- syscall.ForkLock.RLock()
- s, err = socketFunc(family, sotype, proto)
- if err == nil {
- syscall.CloseOnExec(s)
- }
- syscall.ForkLock.RUnlock()
if err != nil {
return -1, os.NewSyscallError("socket", err)
}
- if err = syscall.SetNonblock(s, true); err != nil {
- poll.CloseFunc(s)
- return -1, os.NewSyscallError("setnonblock", err)
- }
return s, nil
}
--- /dev/null
+// Copyright 2024 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.
+
+// This file implements sysSocket for platforms that provide a fast path for
+// setting SetNonblock and CloseOnExec, but don't necessarily support it.
+// Support for SOCK_* flags as part of the type parameter was added to Oracle
+// Solaris in the 11.4 release. Thus, on releases prior to 11.4, we fall back
+// to the combination of socket(3c) and fcntl(2).
+
+package net
+
+import (
+ "internal/poll"
+ "internal/syscall/unix"
+ "os"
+ "syscall"
+)
+
+// Wrapper around the socket system call that marks the returned file
+// descriptor as nonblocking and close-on-exec.
+func sysSocket(family, sotype, proto int) (int, error) {
+ // Perform a cheap test and try the fast path first.
+ if unix.SupportSockNonblockCloexec() {
+ s, err := socketFunc(family, sotype|syscall.SOCK_NONBLOCK|syscall.SOCK_CLOEXEC, proto)
+ if err != nil {
+ return -1, os.NewSyscallError("socket", err)
+ }
+ return s, nil
+ }
+
+ // See ../syscall/exec_unix.go for description of ForkLock.
+ syscall.ForkLock.RLock()
+ s, err := socketFunc(family, sotype, proto)
+ if err == nil {
+ syscall.CloseOnExec(s)
+ }
+ syscall.ForkLock.RUnlock()
+ if err != nil {
+ return -1, os.NewSyscallError("socket", err)
+ }
+ if err = syscall.SetNonblock(s, true); err != nil {
+ poll.CloseFunc(s)
+ return -1, os.NewSyscallError("setnonblock", err)
+ }
+ return s, nil
+}