-
Notifications
You must be signed in to change notification settings - Fork 47
kernelversion: add package for checking kernel versions #205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. | ||
|
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 | ||
) |
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= |
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. |
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 | ||
|
||
// 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 |
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) | ||
} |
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 | ||
} |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 | ||
} |
There was a problem hiding this comment.
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 :)