Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/runtime/export_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const SigeventMaxSize = _sigev_max_size

var NewOSProc0 = newosproc0
var Mincore = mincore
var ParseRelease = parseRelease

type Siginfo siginfo
type Sigevent sigevent
75 changes: 44 additions & 31 deletions src/runtime/os_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -943,53 +943,66 @@ type kernelVersion struct {
}

// getKernelVersion returns major and minor kernel version numbers
// parsed from the uname release field.
func getKernelVersion() kernelVersion {
// parsed from the uname release field. ok reports whether parsing
// succeeded; on failure callers must pick a default rather than
// failing, because this is called during osinit before the runtime
// is fully initialized and a throw here is unrecoverable.
func getKernelVersion() (kv kernelVersion, ok bool) {
var buf linux.Utsname
if e := linux.Uname(&buf); e != 0 {
throw("uname failed")
return kernelVersion{}, false
}

rel := gostringnocopy(&buf.Release[0])
major, minor, _, ok := parseRelease(rel)
if !ok {
throw("failed to parse kernel version from uname")
return kernelVersion{}, false
}
return kernelVersion{major: major, minor: minor}
return kernelVersion{major: major, minor: minor}, true
}

// parseRelease parses a dot-separated version number. It follows the
// semver syntax, but allows the minor and patch versions to be
// elided.
// parseRelease parses a dot-separated version number from the prefix
// of rel. It returns ok=true only if at least the major and minor
// components were successfully parsed; the patch component is
// best-effort. Trailing vendor or build suffixes such as
// "-generic", "+", "_hi3535", or "-rc1" are ignored.
func parseRelease(rel string) (major, minor, patch int, ok bool) {
// Strip anything after a dash or plus.
for i := 0; i < len(rel); i++ {
if rel[i] == '-' || rel[i] == '+' {
rel = rel[:i]
break
// next consumes a run of decimal digits from the front of rel,
// returning the parsed value. If the digits are followed by a
// '.', it is consumed and more is set so the caller knows to
// parse another component; otherwise scanning terminates and
// the rest of rel is discarded.
next := func() (n int, more, ok bool) {
i := 0
for i < len(rel) && rel[i] >= '0' && rel[i] <= '9' {
i++
}
}

next := func() (int, bool) {
for i := 0; i < len(rel); i++ {
if rel[i] == '.' {
ver, err := strconv.Atoi(rel[:i])
rel = rel[i+1:]
return ver, err == nil
}
if i == 0 {
return 0, false, false
}
n, err := strconv.Atoi(rel[:i])
if err != nil {
return 0, false, false
}
if i < len(rel) && rel[i] == '.' {
rel = rel[i+1:]
return n, true, true
}
ver, err := strconv.Atoi(rel)
rel = ""
return ver, err == nil
return n, false, true
}
if major, ok = next(); !ok || rel == "" {
return

var more bool
if major, more, ok = next(); !ok || !more {
return 0, 0, 0, false
}
if minor, ok = next(); !ok || rel == "" {
return
if minor, more, ok = next(); !ok {
return 0, 0, 0, false
}
if !more {
return major, minor, 0, true
}
patch, ok = next()
return
patch, _, _ = next()
return major, minor, patch, true
}

// GE checks if the running kernel version
Expand Down
32 changes: 31 additions & 1 deletion src/runtime/os_linux32.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,38 @@ import (
"unsafe"
)

// configure64bitsTimeOn32BitsArchitectures decides whether to use the
// 64-bit time variants of futex and timer_settime on 32-bit Linux.
//
// The choice is normally made by parsing the kernel release string
// from uname, because probing with -ENOSYS misbehaves on some
// kernels: Android 8.0-10 (API 26-29) has a seccomp filter that kills
// the process on unknown syscalls instead of returning -ENOSYS, and
// some older Synology kernels reuse the new syscall numbers for
// unrelated vendor syscalls, which silently runs the wrong thing.
//
// If the kernel release string can't be parsed, fall back to probing
// futex_time64 with a no-op FUTEX_WAKE: the kernels that motivated
// the version check above all report parseable uname strings, so
// reaching the probing fallback generally means we are on neither,
// and probing is the safest available signal.
func configure64bitsTimeOn32BitsArchitectures() {
use64bitsTimeOn32bits = getKernelVersion().GE(5, 1)
if kv, ok := getKernelVersion(); ok {
use64bitsTimeOn32bits = kv.GE(5, 1)
return
}
use64bitsTimeOn32bits = probeFutexTime64()
}

// probeFutexTime64 reports whether the futex_time64 syscall is
// available on the running kernel. It issues a FUTEX_WAKE_PRIVATE
// with a wake count of 0, which is a no-op if the syscall exists and
// returns -ENOSYS otherwise. timer_settime64 was added to the kernel
// in the same release (Linux 5.1), so a single probe covers both.
func probeFutexTime64() bool {
var word uint32
ret := futex_time64(unsafe.Pointer(&word), _FUTEX_WAKE_PRIVATE, 0, nil, nil, 0)
return ret != -_ENOSYS
}

//go:noescape
Expand Down
30 changes: 30 additions & 0 deletions src/runtime/runtime_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,36 @@ func TestMincoreErrorSign(t *testing.T) {
}
}

func TestParseRelease(t *testing.T) {
tests := []struct {
in string
major, minor, patch int
ok bool
}{
{"6.1.0", 6, 1, 0, true},
{"5.15.0-91-generic", 5, 15, 0, true},
{"4.19.0+", 4, 19, 0, true},
{"6.6.0-rc1", 6, 6, 0, true},
// Synology embedded Linux appends a platform identifier
// after an underscore.
{"3.4.35_hi3535", 3, 4, 35, true},
{"2.6.32_synology", 2, 6, 32, true},
{"3.10", 3, 10, 0, true},
// A single component is not enough; major+minor required.
{"3", 0, 0, 0, false},
{"3-rc1", 0, 0, 0, false},
{"", 0, 0, 0, false},
{"bogus", 0, 0, 0, false},
}
for _, tt := range tests {
major, minor, patch, ok := ParseRelease(tt.in)
if major != tt.major || minor != tt.minor || patch != tt.patch || ok != tt.ok {
t.Errorf("ParseRelease(%q) = (%d, %d, %d, %v); want (%d, %d, %d, %v)",
tt.in, major, minor, patch, ok, tt.major, tt.minor, tt.patch, tt.ok)
}
}
}

func TestKernelStructSize(t *testing.T) {
// Check that the Go definitions of structures exchanged with the kernel are
// the same size as what the kernel defines.
Expand Down
Loading