diff --git a/pkg/sysinfo/sysinfo_linux.go b/pkg/sysinfo/sysinfo_linux.go index f3772078d0..eb59f621ab 100644 --- a/pkg/sysinfo/sysinfo_linux.go +++ b/pkg/sysinfo/sysinfo_linux.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path" + "strconv" "strings" "sync" @@ -12,7 +13,6 @@ import ( "github.com/containerd/cgroups/v3/cgroup1" "github.com/containerd/containerd/pkg/seccomp" "github.com/containerd/log" - "github.com/docker/docker/pkg/parsers" "github.com/moby/sys/mountinfo" ) @@ -320,7 +320,7 @@ func readProcBool(path string) bool { const defaultMaxCPUs = 8192 func isCpusetListAvailable(requested, available string) (bool, error) { - parsedAvailable, err := parsers.ParseUintList(available) + parsedAvailable, err := parseUintList(available, 0) if err != nil { return false, err } @@ -341,7 +341,7 @@ func isCpusetListAvailable(requested, available string) (bool, error) { maxCPUs = m } } - parsedRequested, err := parsers.ParseUintListMaximum(requested, maxCPUs) + parsedRequested, err := parseUintList(requested, maxCPUs) if err != nil { return false, err } @@ -352,3 +352,61 @@ func isCpusetListAvailable(requested, available string) (bool, error) { } return true, nil } + +// parseUintList parses and validates the specified string as the value +// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be +// one of the formats below. Note that duplicates are actually allowed in the +// input string. It returns a `map[int]bool` with available elements from `val` +// set to `true`. Values larger than `maximum` cause an error if max is non zero, +// in order to stop the map becoming excessively large. +// Supported formats: +// +// 7 +// 1-6 +// 0,3-4,7,8-10 +// 0-0,0,1-7 +// 03,1-3 <- this is gonna get parsed as [1,2,3] +// 3,2,1 +// 0-2,3,1 +func parseUintList(val string, maximum int) (map[int]bool, error) { + if val == "" { + return map[int]bool{}, nil + } + + availableInts := make(map[int]bool) + split := strings.Split(val, ",") + errInvalidFormat := fmt.Errorf("invalid format: %s", val) + + for _, r := range split { + if !strings.Contains(r, "-") { + v, err := strconv.Atoi(r) + if err != nil { + return nil, errInvalidFormat + } + if maximum != 0 && v > maximum { + return nil, fmt.Errorf("value of out range, maximum is %d", maximum) + } + availableInts[v] = true + } else { + minS, maxS, _ := strings.Cut(r, "-") + minAvailable, err := strconv.Atoi(minS) + if err != nil { + return nil, errInvalidFormat + } + maxAvailable, err := strconv.Atoi(maxS) + if err != nil { + return nil, errInvalidFormat + } + if maxAvailable < minAvailable { + return nil, errInvalidFormat + } + if maximum != 0 && maxAvailable > maximum { + return nil, fmt.Errorf("value of out range, maximum is %d", maximum) + } + for i := minAvailable; i <= maxAvailable; i++ { + availableInts[i] = true + } + } + } + return availableInts, nil +} diff --git a/pkg/sysinfo/sysinfo_linux_test.go b/pkg/sysinfo/sysinfo_linux_test.go index f309c4132c..c7b021e6b6 100644 --- a/pkg/sysinfo/sysinfo_linux_test.go +++ b/pkg/sysinfo/sysinfo_linux_test.go @@ -3,6 +3,7 @@ package sysinfo // import "github.com/docker/docker/pkg/sysinfo" import ( "os" "path/filepath" + "reflect" "testing" "github.com/containerd/containerd/pkg/seccomp" @@ -93,3 +94,53 @@ func TestIsCpusetListAvailable(t *testing.T) { } } } + +func TestParseUintList(t *testing.T) { + valids := map[string]map[int]bool{ + "": {}, + "7": {7: true}, + "1-6": {1: true, 2: true, 3: true, 4: true, 5: true, 6: true}, + "0-7": {0: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true}, + "0,3-4,7,8-10": {0: true, 3: true, 4: true, 7: true, 8: true, 9: true, 10: true}, + "0-0,0,1-4": {0: true, 1: true, 2: true, 3: true, 4: true}, + "03,1-3": {1: true, 2: true, 3: true}, + "3,2,1": {1: true, 2: true, 3: true}, + "0-2,3,1": {0: true, 1: true, 2: true, 3: true}, + } + for k, v := range valids { + out, err := parseUintList(k, 0) + if err != nil { + t.Fatalf("Expected not to fail, got %v", err) + } + if !reflect.DeepEqual(out, v) { + t.Fatalf("Expected %v, got %v", v, out) + } + } + + invalids := []string{ + "this", + "1--", + "1-10,,10", + "10-1", + "-1", + "-1,0", + } + for _, v := range invalids { + if out, err := parseUintList(v, 0); err == nil { + t.Fatalf("Expected failure with %s but got %v", v, out) + } + } +} + +func TestParseUintListMaximumLimits(t *testing.T) { + v := "10,1000" + if _, err := parseUintList(v, 0); err != nil { + t.Fatalf("Expected not to fail, got %v", err) + } + if _, err := parseUintList(v, 1000); err != nil { + t.Fatalf("Expected not to fail, got %v", err) + } + if out, err := parseUintList(v, 100); err == nil { + t.Fatalf("Expected failure with %s but got %v", v, out) + } +}