Skip to content

Commit 9d4b40e

Browse files
committed
kernelversion: add package for checking kernel versions
This implementation comes from github.com/cyphar/filepath-securejoin's internal/kernelversion package. Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
1 parent 9dc3a90 commit 9d4b40e

File tree

9 files changed

+429
-0
lines changed

9 files changed

+429
-0
lines changed

kernelversion/LICENSE

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Copyright (C) 2022 The Go Authors. All rights reserved.
2+
Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are
6+
met:
7+
8+
* Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
* Redistributions in binary form must reproduce the above
11+
copyright notice, this list of conditions and the following disclaimer
12+
in the documentation and/or other materials provided with the
13+
distribution.
14+
* Neither the name of Google Inc. nor the names of its
15+
contributors may be used to endorse or promote products derived from
16+
this software without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+

kernelversion/go.mod

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module github.com/moby/sys/kernelversion
2+
3+
go 1.18
4+
5+
require (
6+
github.com/stretchr/testify v1.7.1
7+
golang.org/x/sys v0.18.0
8+
)
9+
10+
require (
11+
github.com/davecgh/go-spew v1.1.0 // indirect
12+
github.com/pmezard/go-difflib v1.0.0 // indirect
13+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
14+
)

kernelversion/go.sum

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6+
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
7+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
8+
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
9+
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
10+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
11+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
12+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
13+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## gocompat ##
2+
3+
This directory contains backports of stdlib functions from later Go versions so
4+
that `github.com/moby/sys/kernelversion` can continue to be used by projects
5+
that are stuck with Go 1.18 support.
6+
7+
The source code is licensed under the same license as the Go stdlib. See the
8+
source files for the precise license information.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
//go:build linux && go1.20
3+
4+
// Copyright (C) 2025 SUSE LLC. All rights reserved.
5+
// Use of this source code is governed by a BSD-style
6+
// license that can be found in the LICENSE file.
7+
8+
// Package gocompat includes compatibility shims (backported from future Go
9+
// stdlib versions) to permit filepath-securejoin to be used with older Go
10+
// versions (often filepath-securejoin is added in security patches for old
11+
// releases, so avoiding the need to bump Go compiler requirements is a huge
12+
// plus to downstreams).
13+
package gocompat
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
3+
//go:build linux && go1.21
4+
5+
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
6+
// Use of this source code is governed by a BSD-style
7+
// license that can be found in the LICENSE file.
8+
9+
package gocompat
10+
11+
import (
12+
"cmp"
13+
"sync"
14+
)
15+
16+
// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
17+
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
18+
return sync.OnceValues(f)
19+
}
20+
21+
// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
22+
type CmpOrdered = cmp.Ordered
23+
24+
// CmpCompare is equivalent to Go 1.21's cmp.Compare.
25+
func CmpCompare[T CmpOrdered](x, y T) int {
26+
return cmp.Compare(x, y)
27+
}
28+
29+
// Max2 is equivalent to Go 1.21's max builtin (but only for two parameters).
30+
func Max2[T CmpOrdered](x, y T) T {
31+
return max(x, y)
32+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
3+
//go:build linux && !go1.21
4+
5+
// Copyright (C) 2021, 2022 The Go Authors. All rights reserved.
6+
// Copyright (C) 2024-2025 SUSE LLC. All rights reserved.
7+
// Use of this source code is governed by a BSD-style
8+
// license that can be found in the LICENSE.BSD file.
9+
10+
package gocompat
11+
12+
import (
13+
"sync"
14+
)
15+
16+
// These are very minimal implementations of functions that appear in Go 1.21's
17+
// stdlib, included so that we can build on older Go versions. Most are
18+
// borrowed directly from the stdlib, and a few are modified to be "obviously
19+
// correct" without needing to copy too many other helpers.
20+
21+
// SyncOnceValues is equivalent to Go 1.21's sync.OnceValues.
22+
// Copied from the Go 1.25 stdlib implementation.
23+
func SyncOnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
24+
// Use a struct so that there's a single heap allocation.
25+
d := struct {
26+
f func() (T1, T2)
27+
once sync.Once
28+
valid bool
29+
p any
30+
r1 T1
31+
r2 T2
32+
}{
33+
f: f,
34+
}
35+
return func() (T1, T2) {
36+
d.once.Do(func() {
37+
defer func() {
38+
d.f = nil
39+
d.p = recover()
40+
if !d.valid {
41+
panic(d.p)
42+
}
43+
}()
44+
d.r1, d.r2 = d.f()
45+
d.valid = true
46+
})
47+
if !d.valid {
48+
panic(d.p)
49+
}
50+
return d.r1, d.r2
51+
}
52+
}
53+
54+
// CmpOrdered is equivalent to Go 1.21's cmp.Ordered generic type definition.
55+
// Copied from the Go 1.25 stdlib implementation.
56+
type CmpOrdered interface {
57+
~int | ~int8 | ~int16 | ~int32 | ~int64 |
58+
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
59+
~float32 | ~float64 |
60+
~string
61+
}
62+
63+
// isNaN reports whether x is a NaN without requiring the math package.
64+
// This will always return false if T is not floating-point.
65+
// Copied from the Go 1.25 stdlib implementation.
66+
func isNaN[T CmpOrdered](x T) bool {
67+
return x != x
68+
}
69+
70+
// CmpCompare is equivalent to Go 1.21's cmp.Compare.
71+
// Copied from the Go 1.25 stdlib implementation.
72+
func CmpCompare[T CmpOrdered](x, y T) int {
73+
xNaN := isNaN(x)
74+
yNaN := isNaN(y)
75+
if xNaN {
76+
if yNaN {
77+
return 0
78+
}
79+
return -1
80+
}
81+
if yNaN {
82+
return +1
83+
}
84+
if x < y {
85+
return -1
86+
}
87+
if x > y {
88+
return +1
89+
}
90+
return 0
91+
}
92+
93+
// Max2 is equivalent to Go 1.21's max builtin for two parameters.
94+
func Max2[T CmpOrdered](x, y T) T {
95+
m := x
96+
if y > m {
97+
m = y
98+
}
99+
return m
100+
}

kernelversion/kernel_linux.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
3+
// Copyright (C) 2022 The Go Authors. All rights reserved.
4+
// Copyright (C) 2025 SUSE LLC. All rights reserved.
5+
// Use of this source code is governed by a BSD-style
6+
// license that can be found in the LICENSE.BSD file.
7+
8+
// The parsing logic is very loosely based on the Go stdlib's
9+
// src/internal/syscall/unix/kernel_version_linux.go but with an API that looks
10+
// a bit like runc's libcontainer/system/kernelversion.
11+
12+
// Package kernelversion provides a simple mechanism for checking whether the
13+
// running kernel is at least as new as some baseline kernel version. This is
14+
// often useful when checking for features that would be too complicated to
15+
// test support for (or in cases where we know that some kernel features in
16+
// backport-heavy kernels are broken and need to be avoided).
17+
package kernelversion
18+
19+
import (
20+
"bytes"
21+
"errors"
22+
"fmt"
23+
"strconv"
24+
"strings"
25+
26+
"golang.org/x/sys/unix"
27+
28+
"github.com/moby/sys/kernelversion/internal/gocompat"
29+
)
30+
31+
// KernelVersion is a numeric representation of the key numerical elements of a
32+
// kernel version (for instance, "4.1.2-default-1" would be represented as
33+
// KernelVersion{4, 1, 2}).
34+
type KernelVersion []uint64
35+
36+
func (kver KernelVersion) String() string {
37+
var str strings.Builder
38+
for idx, elem := range kver {
39+
if idx != 0 {
40+
_, _ = str.WriteRune('.')
41+
}
42+
_, _ = str.WriteString(strconv.FormatUint(elem, 10))
43+
}
44+
return str.String()
45+
}
46+
47+
var errInvalidKernelVersion = errors.New("invalid kernel version")
48+
49+
// parseKernelVersion parses a string and creates a KernelVersion based on it.
50+
func parseKernelVersion(kverStr string) (KernelVersion, error) {
51+
kver := make(KernelVersion, 1, 3)
52+
for idx, ch := range kverStr {
53+
if '0' <= ch && ch <= '9' {
54+
v := &kver[len(kver)-1]
55+
*v = (*v * 10) + uint64(ch-'0')
56+
} else {
57+
if idx == 0 || kverStr[idx-1] < '0' || '9' < kverStr[idx-1] {
58+
// "." must be preceded by a digit while in version section
59+
return nil, fmt.Errorf("%w %q: kernel version has dot(s) followed by non-digit in version section", errInvalidKernelVersion, kverStr)
60+
}
61+
if ch != '.' {
62+
break
63+
}
64+
kver = append(kver, 0)
65+
}
66+
}
67+
if len(kver) < 2 {
68+
return nil, fmt.Errorf("%w %q: kernel versions must contain at least two components", errInvalidKernelVersion, kverStr)
69+
}
70+
return kver, nil
71+
}
72+
73+
// getKernelVersion gets the current kernel version.
74+
var getKernelVersion = gocompat.SyncOnceValues(func() (KernelVersion, error) {
75+
var uts unix.Utsname
76+
if err := unix.Uname(&uts); err != nil {
77+
return nil, err
78+
}
79+
// Remove the \x00 from the release.
80+
release := uts.Release[:]
81+
return parseKernelVersion(string(release[:bytes.IndexByte(release, 0)]))
82+
})
83+
84+
// GreaterEqualThan returns true if the the host kernel version is greater than
85+
// or equal to the provided [KernelVersion]. When doing this comparison, any
86+
// non-numerical suffixes of the host kernel version are ignored.
87+
//
88+
// If the number of components provided is not equal to the number of numerical
89+
// components of the host kernel version, any missing components are treated as
90+
// 0. This means that GreaterEqualThan(KernelVersion{4}) will be treated the
91+
// same as GreaterEqualThan(KernelVersion{4, 0, 0, ..., 0, 0}), and that if the
92+
// host kernel version is "4" then GreaterEqualThan(KernelVersion{4, 1}) will
93+
// return false (because the host version will be treated as "4.0").
94+
func GreaterEqualThan(wantKver KernelVersion) (bool, error) {
95+
hostKver, err := getKernelVersion()
96+
if err != nil {
97+
return false, err
98+
}
99+
100+
// Pad out the kernel version lengths to match one another.
101+
cmpLen := gocompat.Max2(len(hostKver), len(wantKver))
102+
hostKver = append(hostKver, make(KernelVersion, cmpLen-len(hostKver))...)
103+
wantKver = append(wantKver, make(KernelVersion, cmpLen-len(wantKver))...)
104+
105+
for i := 0; i < cmpLen; i++ {
106+
switch gocompat.CmpCompare(hostKver[i], wantKver[i]) {
107+
case -1:
108+
// host < want
109+
return false, nil
110+
case +1:
111+
// host > want
112+
return true, nil
113+
case 0:
114+
continue
115+
}
116+
}
117+
// equal version values
118+
return true, nil
119+
}

0 commit comments

Comments
 (0)