diff --git a/internal/util/util.go b/internal/util/util.go index 5039b03ba..8e0ffda07 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -23,7 +23,6 @@ import ( "math" "os" "runtime" - "strconv" "strings" "time" @@ -158,6 +157,39 @@ type KernelVersion struct { Backport bool // backports have a fixed version/patchlevel/sublevel } +// parseKernelRelease parses a kernel release version string into: +// version, patch version, sub version and extra version. +func parseKernelRelease(release string) (int, int, int, int, error) { + version := 0 + patchlevel := 0 + minVersions := 2 + + extra := "" + n, err := fmt.Sscanf(release, "%d.%d%s", &version, &patchlevel, &extra) + if n < minVersions && err != nil { + return 0, 0, 0, 0, fmt.Errorf("failed to parse version and patchlevel from %s: %w", release, err) + } + + sublevel := 0 + extraversion := 0 + if n > minVersions { + n, err = fmt.Sscanf(extra, ".%d%s", &sublevel, &extra) + if err != nil && n == 0 && len(extra) > 0 && extra[0] != '-' && extra[0] == '.' { + return 0, 0, 0, 0, fmt.Errorf("failed to parse subversion from %s: %w", release, err) + } + + extra = strings.TrimPrefix(extra, "-") + // ignore errors, 1st component of extraversion does not need to be an int + _, err = fmt.Sscanf(extra, "%d", &extraversion) + if err != nil { + // "go lint" wants err to be checked... + extraversion = 0 + } + } + + return version, patchlevel, sublevel, extraversion, nil +} + // CheckKernelSupport checks the running kernel and comparing it to known // versions that have support for required features . Distributors of // enterprise Linux have backported quota support to previous versions. This @@ -177,37 +209,11 @@ type KernelVersion struct { // In case the backport bool is false, a simple check for higher versions than // version+patchlevel+sublevel is done. func CheckKernelSupport(release string, supportedVersions []KernelVersion) bool { - vers := strings.Split(strings.SplitN(release, "-", 2)[0], ".") - version, err := strconv.Atoi(vers[0]) + version, patchlevel, sublevel, extraversion, err := parseKernelRelease(release) if err != nil { - ErrorLogMsg("failed to parse version from %s: %v", release, err) + ErrorLogMsg("%v", err) return false } - patchlevel, err := strconv.Atoi(vers[1]) - if err != nil { - ErrorLogMsg("failed to parse patchlevel from %s: %v", release, err) - return false - } - sublevel := 0 - const minLenForSublvl = 3 - if len(vers) >= minLenForSublvl { - sublevel, err = strconv.Atoi(vers[2]) - if err != nil { - ErrorLogMsg("failed to parse sublevel from %s: %v", release, err) - return false - } - } - extra := strings.SplitN(release, "-", 2) - extraversion := 0 - const expectedExtraLen = 2 - if len(extra) == expectedExtraLen { - // ignore errors, 1st component of extraversion does not need to be an int - extraversion, err = strconv.Atoi(strings.Split(extra[1], ".")[0]) - if err != nil { - // "go lint" wants err to be checked... - extraversion = 0 - } - } // compare running kernel against known versions for _, kernel := range supportedVersions { diff --git a/internal/util/util_test.go b/internal/util/util_test.go index 720b0bc9c..3372b7f3d 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -244,6 +244,36 @@ func TestMountOptionsAdd(t *testing.T) { }) } } + +func TestParseKernelRelease(t *testing.T) { + t.Parallel() + + badReleases := []string{"x", "5", "5.", "5.4.", "5.x-2-oops", "4.1.x-7-oh", "5.12.x"} + for _, release := range badReleases { + _, _, _, _, err := parseKernelRelease(release) + if err == nil { + t.Errorf("release %q must not be parsed successfully", release) + } + } + + goodReleases := []string{"5.12", "5.12xlinux", "5.1-2-yam", "3.1-5-x", "5.12.14", "5.12.14xlinux", + "5.12.14-xlinux", "5.12.14-99-x", "3.3x-3"} + goodVersions := [][]int{{5, 12, 0, 0}, {5, 12, 0, 0}, {5, 1, 0, 2}, {3, 1, 0, 5}, + {5, 12, 14, 0}, {5, 12, 14, 0}, {5, 12, 14, 0}, {5, 12, 14, 99}, {3, 3, 0, 0}} + for i, release := range goodReleases { + version, patchlevel, sublevel, extraversion, err := parseKernelRelease(release) + if err != nil { + t.Errorf("parsing error for release %q: %w", release, err) + } + good := goodVersions[i] + if version != good[0] || patchlevel != good[1] || sublevel != good[2] || extraversion != good[3] { + t.Errorf("release %q parsed incorrectly: expected (%d.%d.%d-%d), actual (%d.%d.%d-%d)", + release, good[0], good[1], good[2], good[3], + version, patchlevel, sublevel, extraversion) + } + } +} + func TestCheckKernelSupport(t *testing.T) { t.Parallel() supportsQuota := []string{