Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions kernelversion/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Copyright (C) 2022 The Go Authors. All rights reserved.
Copyright (C) 2024-2025 SUSE LLC. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14 changes: 14 additions & 0 deletions kernelversion/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/moby/sys/kernelversion

go 1.18

require (
github.com/stretchr/testify v1.7.1
golang.org/x/sys v0.18.0
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
13 changes: 13 additions & 0 deletions kernelversion/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
8 changes: 8 additions & 0 deletions kernelversion/internal/gocompat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## gocompat ##

This directory contains backports of stdlib functions from later Go versions so
that `github.com/moby/sys/kernelversion` can continue to be used by projects
that are stuck with Go 1.18 support.

The source code is licensed under the same license as the Go stdlib. See the
source files for the precise license information.
13 changes: 13 additions & 0 deletions kernelversion/internal/gocompat/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux && go1.20
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder why package doc requires go1.20 :)


// Copyright (C) 2025 SUSE LLC. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package gocompat includes compatibility shims (backported from future Go
// stdlib versions) to permit filepath-securejoin to be used with older Go
// versions (often filepath-securejoin is added in security patches for old
// releases, so avoiding the need to bump Go compiler requirements is a huge
// plus to downstreams).
package gocompat
32 changes: 32 additions & 0 deletions kernelversion/internal/gocompat/gocompat_generics_go121.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: BSD-3-Clause

//go:build linux && go1.21

// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gocompat

import (
"cmp"
"sync"
)

// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
return sync.OnceValues(f)
}

// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
type CmpOrdered = cmp.Ordered

// CmpCompare is equivalent to Go 1.21's cmp.Compare.
func CmpCompare[T CmpOrdered](x, y T) int {
return cmp.Compare(x, y)
}

// Max2 is equivalent to Go 1.21's max builtin (but only for two parameters).
func Max2[T CmpOrdered](x, y T) T {
return max(x, y)
}
100 changes: 100 additions & 0 deletions kernelversion/internal/gocompat/gocompat_generics_unsupported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: BSD-3-Clause

//go:build linux && !go1.21

// Copyright (C) 2021, 2022 The Go Authors. All rights reserved.
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.BSD file.

package gocompat

import (
"sync"
)

// These are very minimal implementations of functions that appear in Go 1.21's
// stdlib, included so that we can build on older Go versions. Most are
// borrowed directly from the stdlib, and a few are modified to be "obviously
// correct" without needing to copy too many other helpers.

// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
// Copied from the Go 1.25 stdlib implementation.
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
// Use a struct so that there's a single heap allocation.
d := struct {
f func() (T1, T2)
once sync.Once
valid bool
p any
r1 T1
r2 T2
}{
f: f,
}
return func() (T1, T2) {
d.once.Do(func() {
defer func() {
d.f = nil
d.p = recover()
if !d.valid {
panic(d.p)
}
}()
d.r1, d.r2 = d.f()
d.valid = true
})
if !d.valid {
panic(d.p)
}
return d.r1, d.r2
}
}

// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
// Copied from the Go 1.25 stdlib implementation.
type CmpOrdered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}

// isNaN reports whether x is a NaN without requiring the math package.
// This will always return false if T is not floating-point.
// Copied from the Go 1.25 stdlib implementation.
func isNaN[T CmpOrdered](x T) bool {
return x != x
}

// CmpCompare is equivalent to Go 1.21's cmp.Compare.
// Copied from the Go 1.25 stdlib implementation.
func CmpCompare[T CmpOrdered](x, y T) int {
xNaN := isNaN(x)
yNaN := isNaN(y)
if xNaN {
if yNaN {
return 0
}
return -1
}
if yNaN {
return +1
}
if x < y {
return -1
}
if x > y {
return +1
}
return 0
}

// Max2 is equivalent to Go 1.21's max builtin for two parameters.
func Max2[T CmpOrdered](x, y T) T {
m := x
if y > m {
m = y
}
return m
}
119 changes: 119 additions & 0 deletions kernelversion/kernel_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: BSD-3-Clause

// Copyright (C) 2022 The Go Authors. All rights reserved.
// Copyright (C) 2025 SUSE LLC. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.BSD file.

// The parsing logic is very loosely based on the Go stdlib's
// src/internal/syscall/unix/kernel_version_linux.go but with an API that looks
// a bit like runc's libcontainer/system/kernelversion.

// Package kernelversion provides a simple mechanism for checking whether the
// running kernel is at least as new as some baseline kernel version. This is
// often useful when checking for features that would be too complicated to
// test support for (or in cases where we know that some kernel features in
// backport-heavy kernels are broken and need to be avoided).
package kernelversion

import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"

"golang.org/x/sys/unix"

"github.com/moby/sys/kernelversion/internal/gocompat"
)

// KernelVersion is a numeric representation of the key numerical elements of a
// kernel version (for instance, "4.1.2-default-1" would be represented as
// KernelVersion{4, 1, 2}).
type KernelVersion []uint64

func (kver KernelVersion) String() string {
var str strings.Builder
for idx, elem := range kver {
if idx != 0 {
_, _ = str.WriteRune('.')
}
_, _ = str.WriteString(strconv.FormatUint(elem, 10))
}
return str.String()
}

var errInvalidKernelVersion = errors.New("invalid kernel version")

// parseKernelVersion parses a string and creates a KernelVersion based on it.
func parseKernelVersion(kverStr string) (KernelVersion, error) {
kver := make(KernelVersion, 1, 3)
for idx, ch := range kverStr {
if '0' <= ch && ch <= '9' {
v := &kver[len(kver)-1]
*v = (*v * 10) + uint64(ch-'0')
} else {
if idx == 0 || kverStr[idx-1] < '0' || '9' < kverStr[idx-1] {
// "." must be preceded by a digit while in version section
return nil, fmt.Errorf("%w %q: kernel version has dot(s) followed by non-digit in version section", errInvalidKernelVersion, kverStr)
}
if ch != '.' {
break
}
kver = append(kver, 0)
}
}
if len(kver) < 2 {
return nil, fmt.Errorf("%w %q: kernel versions must contain at least two components", errInvalidKernelVersion, kverStr)
}
return kver, nil
}

// getKernelVersion gets the current kernel version.
var getKernelVersion = gocompat.SyncOnceValues(func() (KernelVersion, error) {
var uts unix.Utsname
if err := unix.Uname(&uts); err != nil {
return nil, err
}
// Remove the \x00 from the release.
release := uts.Release[:]
return parseKernelVersion(string(release[:bytes.IndexByte(release, 0)]))
})

// GreaterEqualThan returns true if the the host kernel version is greater than
// or equal to the provided [KernelVersion]. When doing this comparison, any
// non-numerical suffixes of the host kernel version are ignored.
//
// If the number of components provided is not equal to the number of numerical
// components of the host kernel version, any missing components are treated as
// 0. This means that GreaterEqualThan(KernelVersion{4}) will be treated the
// same as GreaterEqualThan(KernelVersion{4, 0, 0, ..., 0, 0}), and that if the
// host kernel version is "4" then GreaterEqualThan(KernelVersion{4, 1}) will
// return false (because the host version will be treated as "4.0").
func GreaterEqualThan(wantKver KernelVersion) (bool, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proper English would be something like "greater than or equal".

Or, we can use another name, say AtLeast. It will look like

if kernelversion.AtLeast(kernelversion.KernelVersion{4,2})

which is still too much occurrences of "kernel" and "version", but otherwise ok.

Or, we can use a variadic function to simplify callers. Something like:

func AtLeast(want ...uint64)

So the call can look simpler:

if kernelversion.AtLeast(2, 6, 19) {
    ...

Or does it incur greater runtime overhead?

hostKver, err := getKernelVersion()
if err != nil {
return false, err
}

// Pad out the kernel version lengths to match one another.
cmpLen := gocompat.Max2(len(hostKver), len(wantKver))
hostKver = append(hostKver, make(KernelVersion, cmpLen-len(hostKver))...)
wantKver = append(wantKver, make(KernelVersion, cmpLen-len(wantKver))...)

for i := 0; i < cmpLen; i++ {
switch gocompat.CmpCompare(hostKver[i], wantKver[i]) {
case -1:
// host < want
return false, nil
case +1:
// host > want
return true, nil
case 0:
continue
}
}
// equal version values
return true, nil
}
Loading
Loading