Skip to content

Commit 106e36e

Browse files
committed
Use uname machine field to get CPU variant if fails at /proc/cpuinfo
When images/containers in ARM arch were built/executed on x86 host, getCPUVariant will fail as it tries to look for /proc/cpuinfo, whose content is from the host. Adding a new method as fallback to check uname machine when it happens. Signed-off-by: Tony Fang <[email protected]> (cherry picked from commit 8d5c045)
1 parent 3dce8eb commit 106e36e

5 files changed

Lines changed: 238 additions & 102 deletions

File tree

platforms/cpuinfo.go

Lines changed: 5 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,9 @@
1717
package platforms
1818

1919
import (
20-
"bufio"
21-
"fmt"
22-
"os"
2320
"runtime"
24-
"strings"
2521
"sync"
2622

27-
"github.com/containerd/containerd/errdefs"
2823
"github.com/containerd/containerd/log"
2924
)
3025

@@ -37,95 +32,12 @@ var cpuVariantOnce sync.Once
3732
func cpuVariant() string {
3833
cpuVariantOnce.Do(func() {
3934
if isArmArch(runtime.GOARCH) {
40-
cpuVariantValue = getCPUVariant()
35+
var err error
36+
cpuVariantValue, err = getCPUVariant()
37+
if err != nil {
38+
log.L.Errorf("Error getCPUVariant for OS %s : %v", runtime.GOOS, err)
39+
}
4140
}
4241
})
4342
return cpuVariantValue
4443
}
45-
46-
// For Linux, the kernel has already detected the ABI, ISA and Features.
47-
// So we don't need to access the ARM registers to detect platform information
48-
// by ourselves. We can just parse these information from /proc/cpuinfo
49-
func getCPUInfo(pattern string) (info string, err error) {
50-
if !isLinuxOS(runtime.GOOS) {
51-
return "", fmt.Errorf("getCPUInfo for OS %s: %w", runtime.GOOS, errdefs.ErrNotImplemented)
52-
}
53-
54-
cpuinfo, err := os.Open("/proc/cpuinfo")
55-
if err != nil {
56-
return "", err
57-
}
58-
defer cpuinfo.Close()
59-
60-
// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
61-
// the first core is enough.
62-
scanner := bufio.NewScanner(cpuinfo)
63-
for scanner.Scan() {
64-
newline := scanner.Text()
65-
list := strings.Split(newline, ":")
66-
67-
if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
68-
return strings.TrimSpace(list[1]), nil
69-
}
70-
}
71-
72-
// Check whether the scanner encountered errors
73-
err = scanner.Err()
74-
if err != nil {
75-
return "", err
76-
}
77-
78-
return "", fmt.Errorf("getCPUInfo for pattern: %s: %w", pattern, errdefs.ErrNotFound)
79-
}
80-
81-
func getCPUVariant() string {
82-
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
83-
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
84-
// runtime.GOARCH to determine the variants
85-
var variant string
86-
switch runtime.GOARCH {
87-
case "arm64":
88-
variant = "v8"
89-
case "arm":
90-
variant = "v7"
91-
default:
92-
variant = "unknown"
93-
}
94-
95-
return variant
96-
}
97-
98-
variant, err := getCPUInfo("Cpu architecture")
99-
if err != nil {
100-
log.L.WithError(err).Error("failure getting variant")
101-
return ""
102-
}
103-
104-
// handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7")
105-
// https://www.raspberrypi.org/forums/viewtopic.php?t=12614
106-
if runtime.GOARCH == "arm" && variant == "7" {
107-
model, err := getCPUInfo("model name")
108-
if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") {
109-
variant = "6"
110-
}
111-
}
112-
113-
switch strings.ToLower(variant) {
114-
case "8", "aarch64":
115-
variant = "v8"
116-
case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
117-
variant = "v7"
118-
case "6", "6tej":
119-
variant = "v6"
120-
case "5", "5t", "5te", "5tej":
121-
variant = "v5"
122-
case "4", "4t":
123-
variant = "v4"
124-
case "3":
125-
variant = "v3"
126-
default:
127-
variant = "unknown"
128-
}
129-
130-
return variant
131-
}

platforms/cpuinfo_linux.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package platforms
18+
19+
import (
20+
"bufio"
21+
"bytes"
22+
"fmt"
23+
"os"
24+
"runtime"
25+
"strings"
26+
27+
"github.com/containerd/containerd/errdefs"
28+
"golang.org/x/sys/unix"
29+
)
30+
31+
// getMachineArch retrieves the machine architecture through system call
32+
func getMachineArch() (string, error) {
33+
var uname unix.Utsname
34+
err := unix.Uname(&uname)
35+
if err != nil {
36+
return "", err
37+
}
38+
39+
arch := string(uname.Machine[:bytes.IndexByte(uname.Machine[:], 0)])
40+
41+
return arch, nil
42+
}
43+
44+
// For Linux, the kernel has already detected the ABI, ISA and Features.
45+
// So we don't need to access the ARM registers to detect platform information
46+
// by ourselves. We can just parse these information from /proc/cpuinfo
47+
func getCPUInfo(pattern string) (info string, err error) {
48+
49+
cpuinfo, err := os.Open("/proc/cpuinfo")
50+
if err != nil {
51+
return "", err
52+
}
53+
defer cpuinfo.Close()
54+
55+
// Start to Parse the Cpuinfo line by line. For SMP SoC, we parse
56+
// the first core is enough.
57+
scanner := bufio.NewScanner(cpuinfo)
58+
for scanner.Scan() {
59+
newline := scanner.Text()
60+
list := strings.Split(newline, ":")
61+
62+
if len(list) > 1 && strings.EqualFold(strings.TrimSpace(list[0]), pattern) {
63+
return strings.TrimSpace(list[1]), nil
64+
}
65+
}
66+
67+
// Check whether the scanner encountered errors
68+
err = scanner.Err()
69+
if err != nil {
70+
return "", err
71+
}
72+
73+
return "", fmt.Errorf("getCPUInfo for pattern: %s: %w", pattern, errdefs.ErrNotFound)
74+
}
75+
76+
// getCPUVariantFromArch get CPU variant from arch through a system call
77+
func getCPUVariantFromArch(arch string) (string, error) {
78+
79+
var variant string
80+
81+
arch = strings.ToLower(arch)
82+
83+
if arch == "aarch64" {
84+
variant = "8"
85+
} else if len(arch) >= 5 {
86+
//Valid arch format is in form of armvXx
87+
switch arch[3:5] {
88+
case "v8":
89+
variant = "8"
90+
case "v7":
91+
variant = "7"
92+
case "v6":
93+
variant = "6"
94+
case "v5":
95+
variant = "5"
96+
case "v4":
97+
variant = "4"
98+
case "v3":
99+
variant = "3"
100+
default:
101+
variant = "unknown"
102+
}
103+
} else {
104+
return "", fmt.Errorf("getCPUVariantFromArch invalid arch : %s, %v", arch, errdefs.ErrInvalidArgument)
105+
}
106+
return variant, nil
107+
}
108+
109+
// getCPUVariant returns cpu variant for ARM
110+
// We first try reading "Cpu architecture" field from /proc/cpuinfo
111+
// If we can't find it, then fall back using a system call
112+
// This is to cover running ARM in emulated environment on x86 host as this field in /proc/cpuinfo
113+
// was not present.
114+
func getCPUVariant() (string, error) {
115+
116+
variant, err := getCPUInfo("Cpu architecture")
117+
if err != nil {
118+
if errdefs.IsNotFound(err) {
119+
//Let's try getting CPU variant from machine architecture
120+
arch, err := getMachineArch()
121+
if err != nil {
122+
return "", fmt.Errorf("failure getting machine architecture : %v", err)
123+
}
124+
125+
variant, err = getCPUVariantFromArch(arch)
126+
if err != nil {
127+
return "", fmt.Errorf("failure getting CPU variant from machine architecture : %v", err)
128+
}
129+
} else {
130+
return "", fmt.Errorf("failure getting CPU variant : %v", err)
131+
}
132+
}
133+
134+
// handle edge case for Raspberry Pi ARMv6 devices (which due to a kernel quirk, report "CPU architecture: 7")
135+
// https://www.raspberrypi.org/forums/viewtopic.php?t=12614
136+
if runtime.GOARCH == "arm" && variant == "7" {
137+
model, err := getCPUInfo("model name")
138+
if err == nil && strings.HasPrefix(strings.ToLower(model), "armv6-compatible") {
139+
variant = "6"
140+
}
141+
}
142+
143+
switch strings.ToLower(variant) {
144+
case "8", "aarch64":
145+
variant = "v8"
146+
case "7", "7m", "?(12)", "?(13)", "?(14)", "?(15)", "?(16)", "?(17)":
147+
variant = "v7"
148+
case "6", "6tej":
149+
variant = "v6"
150+
case "5", "5t", "5te", "5tej":
151+
variant = "v5"
152+
case "4", "4t":
153+
variant = "v4"
154+
case "3":
155+
variant = "v3"
156+
default:
157+
variant = "unknown"
158+
}
159+
160+
return variant, nil
161+
}
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,18 @@ import (
2222
)
2323

2424
func TestCPUVariant(t *testing.T) {
25-
if !isArmArch(runtime.GOARCH) || !isLinuxOS(runtime.GOOS) {
25+
if !isArmArch(runtime.GOARCH) {
2626
t.Skip("only relevant on linux/arm")
2727
}
2828

2929
variants := []string{"v8", "v7", "v6", "v5", "v4", "v3"}
3030

31-
p := getCPUVariant()
31+
p, err := getCPUVariant()
32+
if err != nil {
33+
t.Fatalf("Error getting CPU variant: %v\n", err)
34+
return
35+
}
36+
3237
for _, variant := range variants {
3338
if p == variant {
3439
t.Logf("got valid variant as expected: %#v = %#v\n", p, variant)
@@ -38,3 +43,11 @@ func TestCPUVariant(t *testing.T) {
3843

3944
t.Fatalf("could not get valid variant as expected: %v\n", variants)
4045
}
46+
47+
func TestGetCPUVariantFromArch(t *testing.T) {
48+
49+
if !isArmArch(runtime.GOARCH) {
50+
t.Skip("only relevant on linux/arm")
51+
}
52+
53+
}

platforms/cpuinfo_other.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//go:build !linux
2+
// +build !linux
3+
4+
/*
5+
Copyright The containerd Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package platforms
21+
22+
import (
23+
"runtime"
24+
)
25+
26+
func getCPUVariant() (string, error) {
27+
28+
var variant string
29+
30+
if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
31+
// Windows/Darwin only supports v7 for ARM32 and v8 for ARM64 and so we can use
32+
// runtime.GOARCH to determine the variants
33+
switch runtime.GOARCH {
34+
case "arm64":
35+
variant = "v8"
36+
case "arm":
37+
variant = "v7"
38+
default:
39+
variant = "unknown"
40+
}
41+
} else if runtime.GOOS == "freebsd" {
42+
// FreeBSD supports ARMv6 and ARMv7 as well as ARMv4 and ARMv5 (though deprecated)
43+
// detecting those variants is currently unimplemented
44+
switch runtime.GOARCH {
45+
case "arm64":
46+
variant = "v8"
47+
default:
48+
variant = "unknown"
49+
}
50+
51+
} else {
52+
return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errdefs.ErrNotImplemented)
53+
54+
}
55+
56+
return variant, nil
57+
}

platforms/database.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,6 @@ import (
2121
"strings"
2222
)
2323

24-
// isLinuxOS returns true if the operating system is Linux.
25-
//
26-
// The OS value should be normalized before calling this function.
27-
func isLinuxOS(os string) bool {
28-
return os == "linux"
29-
}
30-
3124
// These function are generated from https://golang.org/src/go/build/syslist.go.
3225
//
3326
// We use switch statements because they are slightly faster than map lookups

0 commit comments

Comments
 (0)