internal/syscall/unix: add KernelVersionGE

There are a few places in the code which checks that the running kernel
is greater than or equal to x.y. The check takes a few lines and the
checking code is somewhat distracting.

Let's abstract this check into a simple function, KernelVersionGE,
and convert the users accordingly.

Add a test case (I'm not sure it has much value, can be dropped).

Change-Id: I8ec91dcc7452363361f95e46794701c0ae57d956
Reviewed-on: https://go-review.googlesource.com/c/go/+/700796
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Mark Freeman <markfreeman@google.com>
This commit is contained in:
Kir Kolyshkin 2025-09-03 16:12:39 -07:00 committed by Kirill Kolyshkin
parent e603e9834e
commit 68c6a73380
10 changed files with 98 additions and 30 deletions

View file

@ -10,16 +10,11 @@ import (
"syscall"
)
func supportCopyFileRange() bool {
return isKernelVersionGE53()
}
var isKernelVersionGE53 = sync.OnceValue(func() bool {
major, minor := unix.KernelVersion()
var supportCopyFileRange = sync.OnceValue(func() bool {
// copy_file_range(2) is broken in various ways on kernels older than 5.3,
// see https://go.dev/issue/42400 and
// https://man7.org/linux/man-pages/man2/copy_file_range.2.html#VERSIONS
return major > 5 || (major == 5 && minor >= 3)
return unix.KernelVersionGE(5, 3)
})
// For best performance, call copy_file_range() with the largest len value

View file

@ -42,7 +42,9 @@ func KernelVersion() (major, minor int) {
// This function will examine both the kernel version and the availability of the system call.
var SupportCopyFileRange = sync.OnceValue(func() bool {
// The copy_file_range() function first appeared in FreeBSD 13.0.
major, _ := KernelVersion()
if !KernelVersionGE(13, 0) {
return false
}
_, err := CopyFileRange(0, nil, 0, nil, 0, 0)
return major >= 13 && err != syscall.ENOSYS
return err != syscall.ENOSYS
})

View file

@ -0,0 +1,13 @@
// Copyright 2025 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
// KernelVersionGE checks if the running kernel version
// is greater than or equal to the provided version.
func KernelVersionGE(x, y int) bool {
xx, yy := KernelVersion()
return xx > x || (xx == x && yy >= y)
}

View file

@ -0,0 +1,67 @@
// Copyright 2025 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_test
import (
"internal/syscall/unix"
"testing"
)
func TestKernelVersionGE(t *testing.T) {
major, minor := unix.KernelVersion()
t.Logf("Running on kernel %d.%d", major, minor)
tests := []struct {
name string
x, y int
want bool
}{
{
name: "current version equals itself",
x: major,
y: minor,
want: true,
},
{
name: "older major version",
x: major - 1,
y: 0,
want: true,
},
{
name: "same major, older minor version",
x: major,
y: minor - 1,
want: true,
},
{
name: "newer major version",
x: major + 1,
y: 0,
want: false,
},
{
name: "same major, newer minor version",
x: major,
y: minor + 1,
want: false,
},
{
name: "min version (0.0)",
x: 0,
y: 0,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := unix.KernelVersionGE(tt.x, tt.y)
if got != tt.want {
t.Errorf("KernelVersionGE(%d, %d): got %v, want %v", tt.x, tt.y, got, tt.want)
}
})
}
}

View file

@ -77,11 +77,10 @@ var SupportSockNonblockCloexec = sync.OnceValue(func() bool {
}
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 KernelVersionGE(5, 11) // Minimal requirement is SunOS 5.11.
}
return major > 11 || (major == 11 && minor >= 4)
return KernelVersionGE(11, 4)
}
return false
})
@ -101,6 +100,5 @@ var SupportAccept4 = sync.OnceValue(func() bool {
// SupportTCPKeepAliveIdleIntvlCNT determines whether the TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT
// are available by checking the kernel version for Solaris 11.4.
var SupportTCPKeepAliveIdleIntvlCNT = sync.OnceValue(func() bool {
major, minor := KernelVersion()
return major > 11 || (major == 11 && minor >= 4)
return KernelVersionGE(11, 4)
})

View file

@ -53,9 +53,8 @@ func initMPTCPavailable() {
mptcpAvailable = true
}
major, minor := unix.KernelVersion()
// SOL_MPTCP only supported from kernel 5.16
hasSOLMPTCP = major > 5 || (major == 5 && minor >= 16)
// SOL_MPTCP only supported from kernel 5.16.
hasSOLMPTCP = unix.KernelVersionGE(5, 16)
}
func (sd *sysDialer) dialMPTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) {

View file

@ -18,9 +18,8 @@ import (
//
// See issue 5030 and 41470.
func maxAckBacklog(n int) int {
major, minor := unix.KernelVersion()
size := 16
if major > 4 || (major == 4 && minor >= 1) {
if unix.KernelVersionGE(4, 1) {
size = 32
}

View file

@ -11,13 +11,12 @@ import (
func TestMaxAckBacklog(t *testing.T) {
n := 196602
major, minor := unix.KernelVersion()
backlog := maxAckBacklog(n)
expected := 1<<16 - 1
if major > 4 || (major == 4 && minor >= 1) {
if unix.KernelVersionGE(4, 1) {
expected = n
}
if backlog != expected {
t.Fatalf(`Kernel version: "%d.%d", sk_max_ack_backlog mismatch, got %d, want %d`, major, minor, backlog, expected)
t.Fatalf(`sk_max_ack_backlog mismatch, got %d, want %d`, backlog, expected)
}
}

View file

@ -19,7 +19,7 @@ func TestFindExecutableVsNoexec(t *testing.T) {
t.Parallel()
// This test case relies on faccessat2(2) syscall, which appeared in Linux v5.8.
if major, minor := unix.KernelVersion(); major < 5 || (major == 5 && minor < 8) {
if !unix.KernelVersionGE(5, 8) {
t.Skip("requires Linux kernel v5.8 with faccessat2(2) syscall")
}

View file

@ -117,10 +117,6 @@ func TestCPUProfileMultithreadMagnitude(t *testing.T) {
t.Skip("issue 35057 is only confirmed on Linux")
}
// Linux [5.9,5.16) has a kernel bug that can break CPU timers on newly
// created threads, breaking our CPU accounting.
major, minor := unix.KernelVersion()
t.Logf("Running on Linux %d.%d", major, minor)
defer func() {
if t.Failed() {
t.Logf("Failure of this test may indicate that your system suffers from a known Linux kernel bug fixed on newer kernels. See https://golang.org/issue/49065.")
@ -131,9 +127,9 @@ func TestCPUProfileMultithreadMagnitude(t *testing.T) {
// it enabled to potentially warn users that they are on a broken
// kernel.
if testenv.Builder() != "" && (runtime.GOARCH == "386" || runtime.GOARCH == "amd64") {
have59 := major > 5 || (major == 5 && minor >= 9)
have516 := major > 5 || (major == 5 && minor >= 16)
if have59 && !have516 {
// Linux [5.9,5.16) has a kernel bug that can break CPU timers on newly
// created threads, breaking our CPU accounting.
if unix.KernelVersionGE(5, 9) && !unix.KernelVersionGE(5, 16) {
testenv.SkipFlaky(t, 49065)
}
}