diff --git a/src/internal/poll/copy_file_range_linux.go b/src/internal/poll/copy_file_range_linux.go index edaf60fe95..d7cbd98465 100644 --- a/src/internal/poll/copy_file_range_linux.go +++ b/src/internal/poll/copy_file_range_linux.go @@ -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 diff --git a/src/internal/syscall/unix/kernel_version_freebsd.go b/src/internal/syscall/unix/kernel_version_freebsd.go index ef9ee136f3..db3519ddfb 100644 --- a/src/internal/syscall/unix/kernel_version_freebsd.go +++ b/src/internal/syscall/unix/kernel_version_freebsd.go @@ -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 }) diff --git a/src/internal/syscall/unix/kernel_version_ge.go b/src/internal/syscall/unix/kernel_version_ge.go new file mode 100644 index 0000000000..a142404199 --- /dev/null +++ b/src/internal/syscall/unix/kernel_version_ge.go @@ -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) +} diff --git a/src/internal/syscall/unix/kernel_version_ge_test.go b/src/internal/syscall/unix/kernel_version_ge_test.go new file mode 100644 index 0000000000..f4b1b23cd2 --- /dev/null +++ b/src/internal/syscall/unix/kernel_version_ge_test.go @@ -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) + } + }) + } +} diff --git a/src/internal/syscall/unix/kernel_version_solaris.go b/src/internal/syscall/unix/kernel_version_solaris.go index 3f399411d7..7904e1f429 100644 --- a/src/internal/syscall/unix/kernel_version_solaris.go +++ b/src/internal/syscall/unix/kernel_version_solaris.go @@ -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) }) diff --git a/src/net/mptcpsock_linux.go b/src/net/mptcpsock_linux.go index 4223090485..5b58418653 100644 --- a/src/net/mptcpsock_linux.go +++ b/src/net/mptcpsock_linux.go @@ -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) { diff --git a/src/net/sock_linux.go b/src/net/sock_linux.go index cffe9a236f..380a8880d1 100644 --- a/src/net/sock_linux.go +++ b/src/net/sock_linux.go @@ -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 } diff --git a/src/net/sock_linux_test.go b/src/net/sock_linux_test.go index 11303cfff1..9696cddce9 100644 --- a/src/net/sock_linux_test.go +++ b/src/net/sock_linux_test.go @@ -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) } } diff --git a/src/os/exec/lp_linux_test.go b/src/os/exec/lp_linux_test.go index a7f9aa24b8..1436763038 100644 --- a/src/os/exec/lp_linux_test.go +++ b/src/os/exec/lp_linux_test.go @@ -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") } diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index 99c5155806..25a2f3b324 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -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) } }