Repository: opencontainers/runc
Pull request #2820 (2021-02-24 13:24:42)

fs2: fallback to setting io.weight if io.bfq.weight
if bfq is not loaded, then io.bfq.weight is not available. io.weight
should always be available and is the next best equivalent thing.

Signed-off-by: Daniel Dao <dqminh89@gmail.com>


Patch:
diff --git a/.golangci.yml b/.golangci.yml
index d2cd076f..c9f461e8 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -3,7 +3,3 @@
 run:
   build-tags:
     - seccomp
-
-linters:
-  enable:
-    - gofmt
diff --git a/MAINTAINERS b/MAINTAINERS
index 4f386f36..acc3fe12 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2,6 +2,6 @@ Michael Crosby <michael@docker.com> (@crosbymichael)
 Mrunal Patel <mpatel@redhat.com> (@mrunalp)
 Daniel, Dao Quang Minh <dqminh89@gmail.com> (@dqminh)
 Qiang Huang <h.huangqiang@huawei.com> (@hqhq)
-Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
+Aleksa Sarai <asarai@suse.de> (@cyphar)
 Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> (@AkihiroSuda)
 Kir Kolyshkin <kolyshkin@gmail.com> (@kolyshkin)
diff --git a/Makefile b/Makefile
index 6f65d0dc..e13d8f1d 100644
--- a/Makefile
+++ b/Makefile
@@ -14,16 +14,18 @@ COMMIT_NO := $(shell git rev-parse HEAD 2> /dev/null || true)
 COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),"$(COMMIT_NO)-dirty","$(COMMIT_NO)")
 VERSION := $(shell cat ./VERSION)
 
+# TODO: rm -mod=vendor once go 1.13 is unsupported
+ifneq ($(GO111MODULE),off)
+	MOD_VENDOR := "-mod=vendor"
+endif
 ifeq ($(shell $(GO) env GOOS),linux)
 	ifeq (,$(filter $(shell $(GO) env GOARCH),mips mipsle mips64 mips64le ppc64))
-		ifeq (,$(findstring -race,$(EXTRA_FLAGS)))
-			GO_BUILDMODE := "-buildmode=pie"
-		endif
+		GO_BUILDMODE := "-buildmode=pie"
 	endif
 endif
-GO_BUILD := $(GO) build -trimpath $(GO_BUILDMODE) $(EXTRA_FLAGS) -tags "$(BUILDTAGS)" \
+GO_BUILD := $(GO) build -trimpath $(MOD_VENDOR) $(GO_BUILDMODE) $(EXTRA_FLAGS) -tags "$(BUILDTAGS)" \
 	-ldflags "-X main.gitCommit=$(COMMIT) -X main.version=$(VERSION) $(EXTRA_LDFLAGS)"
-GO_BUILD_STATIC := CGO_ENABLED=1 $(GO) build -trimpath $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo" \
+GO_BUILD_STATIC := CGO_ENABLED=1 $(GO) build -trimpath $(MOD_VENDOR) $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo" \
 	-ldflags "-w -extldflags -static -X main.gitCommit=$(COMMIT) -X main.version=$(VERSION) $(EXTRA_LDFLAGS)"
 
 .DEFAULT: runc
@@ -50,7 +52,8 @@ dbuild: runcimage
 		$(RUNC_IMAGE) make clean all
 
 lint:
-	golangci-lint run ./...
+	$(GO) vet $(MOD_VENDOR) ./...
+	$(GO) fmt $(MOD_VENDOR) ./...
 
 man:
 	man/md2man-all.sh
@@ -70,7 +73,7 @@ unittest: runcimage
 		$(RUNC_IMAGE) make localunittest TESTFLAGS=$(TESTFLAGS)
 
 localunittest: all
-	$(GO) test -timeout 3m -tags "$(BUILDTAGS)" $(TESTFLAGS) -v ./...
+	$(GO) test $(MOD_VENDOR) -timeout 3m -tags "$(BUILDTAGS)" $(TESTFLAGS) -v ./...
 
 integration: runcimage
 	$(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \
@@ -115,7 +118,9 @@ clean:
 	rm -rf man/man8
 
 validate:
+	script/validate-gofmt
 	script/validate-c
+	$(GO) vet $(MOD_VENDOR) ./...
 
 shellcheck:
 	shellcheck tests/integration/*.bats tests/integration/*.sh tests/*.sh
@@ -125,6 +130,8 @@ shfmt:
 	shfmt -ln bats -d -w tests/integration/*.bats
 	shfmt -ln bash -d -w man/*.sh script/* tests/*.sh tests/integration/*.bash
 
+ci: validate test release
+
 vendor:
 	$(GO) mod tidy
 	$(GO) mod vendor
@@ -150,5 +157,5 @@ localcross:
 .PHONY: runc all recvtty static release dbuild lint man runcimage \
 	test localtest unittest localunittest integration localintegration \
 	rootlessintegration localrootlessintegration shell install install-bash \
-	install-man clean validate shfmt shellcheck \
+	install-man clean validate ci shfmt shellcheck \
 	vendor verify-dependencies cross localcross
diff --git a/README.md b/README.md
index 71337036..d6018819 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ A third party security audit was performed by Cure53, you can see the full repor
 ## Building
 
 `runc` currently supports the Linux platform with various architecture support.
-It must be built with Go version 1.14 or higher.
+It must be built with Go version 1.13 or higher.
 
 In order to enable seccomp support you will need to install `libseccomp` on your platform.
 > e.g. `libseccomp-devel` for CentOS, or `libseccomp-dev` for Ubuntu
diff --git a/libcontainer/cgroups/cgroups.go b/libcontainer/cgroups/cgroups.go
index c418fbae..a16a68e9 100644
--- a/libcontainer/cgroups/cgroups.go
+++ b/libcontainer/cgroups/cgroups.go
@@ -48,7 +48,4 @@ type Manager interface {
 
 	// Whether the cgroup path exists or not
 	Exists() bool
-
-	// OOMKillCount reports OOM kill count for the cgroup.
-	OOMKillCount() (uint64, error)
 }
diff --git a/libcontainer/cgroups/fs/cpu.go b/libcontainer/cgroups/fs/cpu.go
index 194f3a54..1d5f455d 100644
--- a/libcontainer/cgroups/fs/cpu.go
+++ b/libcontainer/cgroups/fs/cpu.go
@@ -97,7 +97,7 @@ func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
 
 	sc := bufio.NewScanner(f)
 	for sc.Scan() {
-		t, v, err := fscommon.ParseKeyValue(sc.Text())
+		t, v, err := fscommon.GetCgroupParamKeyValue(sc.Text())
 		if err != nil {
 			return err
 		}
diff --git a/libcontainer/cgroups/fs/fs.go b/libcontainer/cgroups/fs/fs.go
index bede91e2..a42ce453 100644
--- a/libcontainer/cgroups/fs/fs.go
+++ b/libcontainer/cgroups/fs/fs.go
@@ -9,7 +9,6 @@ import (
 	"sync"
 
 	"github.com/opencontainers/runc/libcontainer/cgroups"
-	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
 	"github.com/opencontainers/runc/libcontainer/configs"
 	libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
 	"github.com/pkg/errors"
@@ -422,11 +421,3 @@ func (m *manager) GetFreezerState() (configs.FreezerState, error) {
 func (m *manager) Exists() bool {
 	return cgroups.PathExists(m.Path("devices"))
 }
-
-func OOMKillCount(path string) (uint64, error) {
-	return fscommon.GetValueByKey(path, "memory.oom_control", "oom_kill")
-}
-
-func (m *manager) OOMKillCount() (uint64, error) {
-	return OOMKillCount(m.Path("memory"))
-}
diff --git a/libcontainer/cgroups/fs/memory.go b/libcontainer/cgroups/fs/memory.go
index 5d52d325..61be6d7e 100644
--- a/libcontainer/cgroups/fs/memory.go
+++ b/libcontainer/cgroups/fs/memory.go
@@ -14,15 +14,11 @@ import (
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
 	"github.com/opencontainers/runc/libcontainer/configs"
-	"github.com/pkg/errors"
-	"golang.org/x/sys/unix"
 )
 
 const (
 	cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes"
 	cgroupMemoryLimit     = "memory.limit_in_bytes"
-	cgroupMemoryUsage     = "memory.usage_in_bytes"
-	cgroupMemoryMaxUsage  = "memory.max_usage_in_bytes"
 )
 
 type MemoryGroup struct {
@@ -61,52 +57,20 @@ func (s *MemoryGroup) Apply(path string, d *cgroupData) (err error) {
 	return join(path, d.pid)
 }
 
-func setMemory(path string, val int64) error {
-	if val == 0 {
-		return nil
-	}
-
-	err := fscommon.WriteFile(path, cgroupMemoryLimit, strconv.FormatInt(val, 10))
-	if !errors.Is(err, unix.EBUSY) {
-		return err
-	}
-
-	// EBUSY means the kernel can't set new limit as it's too low
-	// (lower than the current usage). Return more specific error.
-	usage, err := fscommon.GetCgroupParamUint(path, cgroupMemoryUsage)
-	if err != nil {
-		return err
-	}
-	max, err := fscommon.GetCgroupParamUint(path, cgroupMemoryMaxUsage)
-	if err != nil {
-		return err
-	}
-
-	return errors.Errorf("unable to set memory limit to %d (current usage: %d, peak usage: %d)", val, usage, max)
-}
-
-func setSwap(path string, val int64) error {
-	if val == 0 {
-		return nil
-	}
-
-	return fscommon.WriteFile(path, cgroupMemorySwapLimit, strconv.FormatInt(val, 10))
-}
-
-func setMemoryAndSwap(path string, r *configs.Resources) error {
+func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error {
 	// If the memory update is set to -1 and the swap is not explicitly
 	// set, we should also set swap to -1, it means unlimited memory.
-	if r.Memory == -1 && r.MemorySwap == 0 {
+	if cgroup.Resources.Memory == -1 && cgroup.Resources.MemorySwap == 0 {
 		// Only set swap if it's enabled in kernel
 		if cgroups.PathExists(filepath.Join(path, cgroupMemorySwapLimit)) {
-			r.MemorySwap = -1
+			cgroup.Resources.MemorySwap = -1
 		}
 	}
 
 	// When memory and swap memory are both set, we need to handle the cases
 	// for updating container.
-	if r.Memory != 0 && r.MemorySwap != 0 {
-		curLimit, err := fscommon.GetCgroupParamUint(path, cgroupMemoryLimit)
+	if cgroup.Resources.Memory != 0 && cgroup.Resources.MemorySwap != 0 {
+		memoryUsage, err := getMemoryData(path, "")
 		if err != nil {
 			return err
 		}
@@ -114,29 +78,39 @@ func setMemoryAndSwap(path string, r *configs.Resources) error {
 		// When update memory limit, we should adapt the write sequence
 		// for memory and swap memory, so it won't fail because the new
 		// value and the old value don't fit kernel's validation.
-		if r.MemorySwap == -1 || curLimit < uint64(r.MemorySwap) {
-			if err := setSwap(path, r.MemorySwap); err != nil {
+		if cgroup.Resources.MemorySwap == -1 || memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) {
+			if err := fscommon.WriteFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
 				return err
 			}
-			if err := setMemory(path, r.Memory); err != nil {
+			if err := fscommon.WriteFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
+				return err
+			}
+		} else {
+			if err := fscommon.WriteFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
+				return err
+			}
+			if err := fscommon.WriteFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
+				return err
+			}
+		}
+	} else {
+		if cgroup.Resources.Memory != 0 {
+			if err := fscommon.WriteFile(path, cgroupMemoryLimit, strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
+				return err
+			}
+		}
+		if cgroup.Resources.MemorySwap != 0 {
+			if err := fscommon.WriteFile(path, cgroupMemorySwapLimit, strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
 				return err
 			}
-			return nil
 		}
-	}
-
-	if err := setMemory(path, r.Memory); err != nil {
-		return err
-	}
-	if err := setSwap(path, r.MemorySwap); err != nil {
-		return err
 	}
 
 	return nil
 }
 
 func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
-	if err := setMemoryAndSwap(path, cgroup.Resources); err != nil {
+	if err := setMemoryAndSwap(path, cgroup); err != nil {
 		return err
 	}
 
@@ -188,7 +162,7 @@ func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
 
 	sc := bufio.NewScanner(statsFile)
 	for sc.Scan() {
-		t, v, err := fscommon.ParseKeyValue(sc.Text())
+		t, v, err := fscommon.GetCgroupParamKeyValue(sc.Text())
 		if err != nil {
 			return fmt.Errorf("failed to parse memory.stat (%q) - %v", sc.Text(), err)
 		}
diff --git a/libcontainer/cgroups/fs2/cpu.go b/libcontainer/cgroups/fs2/cpu.go
index b7331338..0dffe664 100644
--- a/libcontainer/cgroups/fs2/cpu.go
+++ b/libcontainer/cgroups/fs2/cpu.go
@@ -57,7 +57,7 @@ func statCpu(dirPath string, stats *cgroups.Stats) error {
 
 	sc := bufio.NewScanner(f)
 	for sc.Scan() {
-		t, v, err := fscommon.ParseKeyValue(sc.Text())
+		t, v, err := fscommon.GetCgroupParamKeyValue(sc.Text())
 		if err != nil {
 			return err
 		}
diff --git a/libcontainer/cgroups/fs2/fs2.go b/libcontainer/cgroups/fs2/fs2.go
index 733f9dcf..3f0b9e0d 100644
--- a/libcontainer/cgroups/fs2/fs2.go
+++ b/libcontainer/cgroups/fs2/fs2.go
@@ -257,11 +257,3 @@ func (m *manager) GetFreezerState() (configs.FreezerState, error) {
 func (m *manager) Exists() bool {
 	return cgroups.PathExists(m.dirPath)
 }
-
-func OOMKillCount(path string) (uint64, error) {
-	return fscommon.GetValueByKey(path, "memory.events", "oom_kill")
-}
-
-func (m *manager) OOMKillCount() (uint64, error) {
-	return OOMKillCount(m.dirPath)
-}
diff --git a/libcontainer/cgroups/fs2/hugetlb.go b/libcontainer/cgroups/fs2/hugetlb.go
index feddc7aa..18cd411c 100644
--- a/libcontainer/cgroups/fs2/hugetlb.go
+++ b/libcontainer/cgroups/fs2/hugetlb.go
@@ -44,10 +44,14 @@ func statHugeTlb(dirPath string, stats *cgroups.Stats) error {
 		hugetlbStats.Usage = value
 
 		fileName := "hugetlb." + pagesize + ".events"
-		value, err = fscommon.GetValueByKey(dirPath, fileName, "max")
+		contents, err := fscommon.ReadFile(dirPath, fileName)
 		if err != nil {
 			return errors.Wrap(err, "failed to read stats")
 		}
+		_, value, err = fscommon.GetCgroupParamKeyValue(contents)
+		if err != nil {
+			return errors.Wrap(err, "failed to parse "+fileName)
+		}
 		hugetlbStats.Failcnt = value
 
 		stats.HugetlbStats[pagesize] = hugetlbStats
diff --git a/libcontainer/cgroups/fs2/io.go b/libcontainer/cgroups/fs2/io.go
index 4c4d656c..34262d3c 100644
--- a/libcontainer/cgroups/fs2/io.go
+++ b/libcontainer/cgroups/fs2/io.go
@@ -30,7 +30,15 @@ func setIo(dirPath string, cgroup *configs.Cgroup) error {
 		filename := "io.bfq.weight"
 		if err := fscommon.WriteFile(dirPath, filename,
 			strconv.FormatUint(uint64(cgroup.Resources.BlkioWeight), 10)); err != nil {
-			return err
+			// if io.bfq.weight does not exist, then bfq module is not loaded.
+			// Fallback to use io.weight with a conversion scheme
+			if !os.IsNotExist(err) {
+				return err
+			}
+			v := cgroups.ConvertBlkIOToIOWeightValue(cgroup.Resources.BlkioWeight)
+			if err := fscommon.WriteFile(dirPath, "io.weight", strconv.FormatUint(v, 10)); err != nil {
+				return err
+			}
 		}
 	}
 	for _, td := range cgroup.Resources.BlkioThrottleReadBpsDevice {
diff --git a/libcontainer/cgroups/fs2/memory.go b/libcontainer/cgroups/fs2/memory.go
index cc8eaeb2..81b28df7 100644
--- a/libcontainer/cgroups/fs2/memory.go
+++ b/libcontainer/cgroups/fs2/memory.go
@@ -82,7 +82,7 @@ func statMemory(dirPath string, stats *cgroups.Stats) error {
 
 	sc := bufio.NewScanner(statsFile)
 	for sc.Scan() {
-		t, v, err := fscommon.ParseKeyValue(sc.Text())
+		t, v, err := fscommon.GetCgroupParamKeyValue(sc.Text())
 		if err != nil {
 			return errors.Wrapf(err, "failed to parse memory.stat (%q)", sc.Text())
 		}
diff --git a/libcontainer/cgroups/fscommon/utils.go b/libcontainer/cgroups/fscommon/utils.go
index db0caded..2e4e837f 100644
--- a/libcontainer/cgroups/fscommon/utils.go
+++ b/libcontainer/cgroups/fscommon/utils.go
@@ -35,42 +35,22 @@ func ParseUint(s string, base, bitSize int) (uint64, error) {
 	return value, nil
 }
 
-// ParseKeyValue parses a space-separated "name value" kind of cgroup
-// parameter and returns its key as a string, and its value as uint64
-// (ParseUint is used to convert the value). For example,
-// "io_service_bytes 1234" will be returned as "io_service_bytes", 1234.
-func ParseKeyValue(t string) (string, uint64, error) {
-	parts := strings.SplitN(t, " ", 3)
-	if len(parts) != 2 {
-		return "", 0, fmt.Errorf("line %q is not in key value format", t)
-	}
-
-	value, err := ParseUint(parts[1], 10, 64)
-	if err != nil {
-		return "", 0, fmt.Errorf("unable to convert to uint64: %v", err)
-	}
-
-	return parts[0], value, nil
-}
-
-// GetValueByKey reads a key-value pairs from the specified cgroup file,
-// and returns a value of the specified key. ParseUint is used for value
-// conversion.
-func GetValueByKey(path, file, key string) (uint64, error) {
-	content, err := ReadFile(path, file)
-	if err != nil {
-		return 0, err
-	}
-
-	lines := strings.Split(string(content), "\n")
-	for _, line := range lines {
-		arr := strings.Split(line, " ")
-		if len(arr) == 2 && arr[0] == key {
-			return ParseUint(arr[1], 10, 64)
+// GetCgroupParamKeyValue parses a space-separated "name value" kind of cgroup
+// parameter and returns its components. For example, "io_service_bytes 1234"
+// will return as "io_service_bytes", 1234.
+func GetCgroupParamKeyValue(t string) (string, uint64, error) {
+	parts := strings.Fields(t)
+	switch len(parts) {
+	case 2:
+		value, err := ParseUint(parts[1], 10, 64)
+		if err != nil {
+			return "", 0, fmt.Errorf("unable to convert to uint64: %v", err)
 		}
-	}
 
-	return 0, nil
+		return parts[0], value, nil
+	default:
+		return "", 0, ErrNotValidFormat
+	}
 }
 
 // GetCgroupParamUint reads a single uint64 value from the specified cgroup file.
diff --git a/libcontainer/cgroups/systemd/v1.go b/libcontainer/cgroups/systemd/v1.go
index 59a8b1e7..2ad1347e 100644
--- a/libcontainer/cgroups/systemd/v1.go
+++ b/libcontainer/cgroups/systemd/v1.go
@@ -450,7 +450,3 @@ func (m *legacyManager) GetFreezerState() (configs.FreezerState, error) {
 func (m *legacyManager) Exists() bool {
 	return cgroups.PathExists(m.Path("devices"))
 }
-
-func (m *legacyManager) OOMKillCount() (uint64, error) {
-	return fs.OOMKillCount(m.Path("memory"))
-}
diff --git a/libcontainer/cgroups/systemd/v2.go b/libcontainer/cgroups/systemd/v2.go
index 65abc059..6f142265 100644
--- a/libcontainer/cgroups/systemd/v2.go
+++ b/libcontainer/cgroups/systemd/v2.go
@@ -495,7 +495,3 @@ func (m *unifiedManager) GetFreezerState() (configs.FreezerState, error) {
 func (m *unifiedManager) Exists() bool {
 	return cgroups.PathExists(m.path)
 }
-
-func (m *unifiedManager) OOMKillCount() (uint64, error) {
-	return fs2.OOMKillCount(m.path)
-}
diff --git a/libcontainer/cgroups/utils.go b/libcontainer/cgroups/utils.go
index a542b538..f6d04d58 100644
--- a/libcontainer/cgroups/utils.go
+++ b/libcontainer/cgroups/utils.go
@@ -439,3 +439,14 @@ func ConvertMemorySwapToCgroupV2Value(memorySwap, memory int64) (int64, error) {
 
 	return memorySwap - memory, nil
 }
+
+// Since the OCI spec is designed for cgroup v1, in some cases
+// there is need to convert from the cgroup v1 configuration to cgroup v2
+// the formula for BlkIOWeight to IOWeight is y = (1 + (x - 10) * 9999 / 990)
+// convert linearly from [10-1000] to [1-10000]
+func ConvertBlkIOToIOWeightValue(blkIoWeight uint16) uint64 {
+	if blkIoWeight == 0 {
+		return 0
+	}
+	return uint64(1 + (uint64(blkIoWeight)-10)*9999/990)
+}
diff --git a/libcontainer/configs/config.go b/libcontainer/configs/config.go
index 3808f79a..e1cd1626 100644
--- a/libcontainer/configs/config.go
+++ b/libcontainer/configs/config.go
@@ -387,7 +387,7 @@ func (c Command) Run(s *specs.State) error {
 		return err
 	case <-timerCh:
 		cmd.Process.Kill()
-		<-errC
+		cmd.Wait()
 		return fmt.Errorf("hook ran past specified timeout of %.1fs", c.Timeout.Seconds())
 	}
 }
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index 65e2eace..3dca29e4 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -570,7 +570,6 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, messageSockP
 		intelRdtPath:    state.IntelRdtPath,
 		messageSockPair: messageSockPair,
 		logFilePair:     logFilePair,
-		manager:         c.cgroupManager,
 		config:          c.newInitConfig(p),
 		process:         p,
 		bootstrapData:   data,
diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go
index 0b4cf59e..5cd37416 100644
--- a/libcontainer/factory_linux.go
+++ b/libcontainer/factory_linux.go
@@ -185,7 +185,7 @@ func CriuPath(criupath string) func(*LinuxFactory) error {
 // configures the factory with the provided option funcs.
 func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
 	if root != "" {
-		if err := os.MkdirAll(root, 0o700); err != nil {
+		if err := os.MkdirAll(root, 0700); err != nil {
 			return nil, newGenericError(err, SystemError)
 		}
 	}
@@ -225,7 +225,7 @@ type LinuxFactory struct {
 	// containers.
 	CriuPath string
 
-	// New{u,g}idmapPath is the path to the binaries used for mapping with
+	// New{u,g}uidmapPath is the path to the binaries used for mapping with
 	// rootless containers.
 	NewuidmapPath string
 	NewgidmapPath string
@@ -259,7 +259,7 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err
 	} else if !os.IsNotExist(err) {
 		return nil, newGenericError(err, SystemError)
 	}
-	if err := os.MkdirAll(containerRoot, 0o711); err != nil {
+	if err := os.MkdirAll(containerRoot, 0711); err != nil {
 		return nil, newGenericError(err, SystemError)
 	}
 	if err := os.Chown(containerRoot, unix.Geteuid(), unix.Getegid()); err != nil {
diff --git a/libcontainer/notify_linux_v2.go b/libcontainer/notify_linux_v2.go
index dd0ec290..cdab10ed 100644
--- a/libcontainer/notify_linux_v2.go
+++ b/libcontainer/notify_linux_v2.go
@@ -3,28 +3,48 @@
 package libcontainer
 
 import (
+	"io/ioutil"
 	"path/filepath"
+	"strconv"
+	"strings"
 	"unsafe"
 
-	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"golang.org/x/sys/unix"
 )
 
+func getValueFromCgroup(path, key string) (int, error) {
+	content, err := ioutil.ReadFile(path)
+	if err != nil {
+		return 0, err
+	}
+
+	lines := strings.Split(string(content), "\n")
+	for _, line := range lines {
+		arr := strings.Split(line, " ")
+		if len(arr) == 2 && arr[0] == key {
+			return strconv.Atoi(arr[1])
+		}
+	}
+	return 0, nil
+}
+
 func registerMemoryEventV2(cgDir, evName, cgEvName string) (<-chan struct{}, error) {
+	eventControlPath := filepath.Join(cgDir, evName)
+	cgEvPath := filepath.Join(cgDir, cgEvName)
 	fd, err := unix.InotifyInit()
 	if err != nil {
 		return nil, errors.Wrap(err, "unable to init inotify")
 	}
 	// watching oom kill
-	evFd, err := unix.InotifyAddWatch(fd, filepath.Join(cgDir, evName), unix.IN_MODIFY)
+	evFd, err := unix.InotifyAddWatch(fd, eventControlPath, unix.IN_MODIFY)
 	if err != nil {
 		unix.Close(fd)
 		return nil, errors.Wrap(err, "unable to add inotify watch")
 	}
 	// Because no `unix.IN_DELETE|unix.IN_DELETE_SELF` event for cgroup file system, so watching all process exited
-	cgFd, err := unix.InotifyAddWatch(fd, filepath.Join(cgDir, cgEvName), unix.IN_MODIFY)
+	cgFd, err := unix.InotifyAddWatch(fd, cgEvPath, unix.IN_MODIFY)
 	if err != nil {
 		unix.Close(fd)
 		return nil, errors.Wrap(err, "unable to add inotify watch")
@@ -59,12 +79,12 @@ func registerMemoryEventV2(cgDir, evName, cgEvName string) (<-chan struct{}, err
 				}
 				switch int(rawEvent.Wd) {
 				case evFd:
-					oom, err := fscommon.GetValueByKey(cgDir, evName, "oom_kill")
+					oom, err := getValueFromCgroup(eventControlPath, "oom_kill")
 					if err != nil || oom > 0 {
 						ch <- struct{}{}
 					}
 				case cgFd:
-					pids, err := fscommon.GetValueByKey(cgDir, cgEvName, "populated")
+					pids, err := getValueFromCgroup(cgEvPath, "populated")
 					if err != nil || pids == 0 {
 						return
 					}
diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go
index 6f282877..834268b9 100644
--- a/libcontainer/process_linux.go
+++ b/libcontainer/process_linux.go
@@ -65,7 +65,6 @@ type setnsProcess struct {
 	logFilePair     filePair
 	cgroupPaths     map[string]string
 	rootlessCgroups bool
-	manager         cgroups.Manager
 	intelRdtPath    string
 	config          *initConfig
 	fds             []string
@@ -89,8 +88,6 @@ func (p *setnsProcess) signal(sig os.Signal) error {
 
 func (p *setnsProcess) start() (retErr error) {
 	defer p.messageSockPair.parent.Close()
-	// get the "before" value of oom kill count
-	oom, _ := p.manager.OOMKillCount()
 	err := p.cmd.Start()
 	// close the write-side of the pipes (controlled by child)
 	p.messageSockPair.child.Close()
@@ -100,10 +97,6 @@ func (p *setnsProcess) start() (retErr error) {
 	}
 	defer func() {
 		if retErr != nil {
-			if newOom, err := p.manager.OOMKillCount(); err == nil && newOom != oom {
-				// Someone in this cgroup was killed, this _might_ be us.
-				retErr = newSystemErrorWithCause(retErr, "possibly OOM-killed")
-			}
 			err := ignoreTerminateErrors(p.terminate())
 			if err != nil {
 				logrus.WithError(err).Warn("unable to terminate setnsProcess")
@@ -328,24 +321,6 @@ func (p *initProcess) start() (retErr error) {
 	}
 	defer func() {
 		if retErr != nil {
-			// init might be killed by the kernel's OOM killer.
-			oom, err := p.manager.OOMKillCount()
-			if err != nil {
-				logrus.WithError(err).Warn("unable to get oom kill count")
-			} else if oom > 0 {
-				// Does not matter what the particular error was,
-				// its cause is most probably OOM, so report that.
-				const oomError = "container init was OOM-killed (memory limit too low?)"
-
-				if logrus.GetLevel() >= logrus.DebugLevel {
-					// Only show the original error if debug is set,
-					// as it is not generally very useful.
-					retErr = newSystemErrorWithCause(retErr, oomError)
-				} else {
-					retErr = newSystemError(errors.New(oomError))
-				}
-			}
-
 			// terminate the process to ensure we can remove cgroups
 			if err := ignoreTerminateErrors(p.terminate()); err != nil {
 				logrus.WithError(err).Warn("unable to terminate initProcess")
diff --git a/script/validate-gofmt b/script/validate-gofmt
new file mode 100755
index 00000000..8337ed2d
--- /dev/null
+++ b/script/validate-gofmt
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+source "$(dirname "$BASH_SOURCE")/.validate"
+
+IFS=$'\n'
+files=($(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true))
+unset IFS
+
+badFiles=()
+for f in "${files[@]}"; do
+	# we use "git show" here to validate that what's committed is formatted
+	if [ "$(git show "$VALIDATE_HEAD:$f" | gofmt -s -l)" ]; then
+		badFiles+=("$f")
+	fi
+done
+
+if [ ${#badFiles[@]} -eq 0 ]; then
+	echo 'Congratulations!  All Go source files are properly formatted.'
+else
+	{
+		echo "These files are not properly gofmt'd:"
+		for f in "${badFiles[@]}"; do
+			echo " - $f"
+		done
+		echo
+		echo 'Please reformat the above files using "gofmt -s -w" and commit the result.'
+		echo
+	} >&2
+	false
+fi


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 704bf07c..b59edd65 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -21,7 +21,6 @@ jobs:
       matrix:
         go-version: [1.14.x, 1.15.x, 1.16.x]
         rootless: ["rootless", ""]
-        race: ["-race", ""]
 
     steps:
 
@@ -42,7 +41,7 @@ jobs:
         go-version: ${{ matrix.go-version }}
 
     - name: build
-      run: sudo -E PATH="$PATH" make EXTRA_FLAGS="${{ matrix.race }}" all
+      run: sudo -E PATH="$PATH" make all
 
     - name: install bats
       uses: mig4/setup-bats@v1
@@ -51,7 +50,7 @@ jobs:
 
     - name: unit test
       if: matrix.rootless != 'rootless'
-      run: sudo -E PATH="$PATH" -- make TESTFLAGS="${{ matrix.race }}" localunittest
+      run: sudo -E PATH="$PATH" -- make localunittest
 
     - name: add rootless user
       if: matrix.rootless == 'rootless'
@@ -138,33 +137,3 @@ jobs:
       # FIXME: rootless is skipped because of EPERM on writing cgroup.procs
         if: false
         run: ssh default "sudo -i make -C /vagrant localrootlessintegration"
-
-  # We need to continue support for 32-bit ARM.
-  # However, we do not have 32-bit ARM CI, so we use i386 for testing 32bit stuff.
-  # We are not interested in providing official support for i386.
-  cross-i386:
-    runs-on: ubuntu-20.04
-
-    steps:
-
-    - name: checkout
-      uses: actions/checkout@v2
-
-    - name: install deps
-      run: |
-        sudo dpkg --add-architecture i386
-        # add criu repo
-        sudo add-apt-repository -y ppa:criu/ppa
-        # apt-add-repository runs apt update so we don't have to.
-
-        # Due to a bug in apt, we have to update it first
-        # (see https://bugs.launchpad.net/ubuntu-cdimage/+bug/1871268)
-        sudo apt -q install apt
-        sudo apt -q install libseccomp-dev libseccomp-dev:i386 gcc-multilib criu
-
-    - name: install go
-      uses: actions/setup-go@v2 # use default Go version
-
-    - name: unit test
-      # cgo is disabled by default when cross-compiling
-      run: sudo -E PATH="$PATH" -- make GOARCH=386 CGO_ENABLED=1 localunittest
diff --git a/libcontainer/cgroups/fs/cpu_test.go b/libcontainer/cgroups/fs/cpu_test.go
index 9adc9b31..4a8ecf99 100644
--- a/libcontainer/cgroups/fs/cpu_test.go
+++ b/libcontainer/cgroups/fs/cpu_test.go
@@ -112,7 +112,7 @@ func TestCpuStats(t *testing.T) {
 		throttledTime = uint64(18446744073709551615)
 	)
 
-	cpuStatContent := fmt.Sprintf("nr_periods %d\nnr_throttled %d\nthrottled_time %d\n",
+	cpuStatContent := fmt.Sprintf("nr_periods %d\n nr_throttled %d\n throttled_time %d\n",
 		nrPeriods, nrThrottled, throttledTime)
 	helper.writeFileContents(map[string]string{
 		"cpu.stat": cpuStatContent,
diff --git a/libcontainer/cgroups/fs/memory_test.go b/libcontainer/cgroups/fs/memory_test.go
index 44f1c9a9..00fb338b 100644
--- a/libcontainer/cgroups/fs/memory_test.go
+++ b/libcontainer/cgroups/fs/memory_test.go
@@ -165,6 +165,11 @@ func TestMemorySetSwapSmallerThanMemory(t *testing.T) {
 	helper.writeFileContents(map[string]string{
 		"memory.limit_in_bytes":       strconv.Itoa(memoryBefore),
 		"memory.memsw.limit_in_bytes": strconv.Itoa(memoryswapBefore),
+		// Set will call getMemoryData when memory and swap memory are
+		// both set, fake these fields so we don't get error.
+		"memory.usage_in_bytes":     "0",
+		"memory.max_usage_in_bytes": "0",
+		"memory.failcnt":            "0",
 	})
 
 	helper.CgroupData.config.Resources.Memory = memoryAfter
@@ -179,14 +184,14 @@ func TestMemorySetSwapSmallerThanMemory(t *testing.T) {
 		t.Fatalf("Failed to parse memory.limit_in_bytes - %s", err)
 	}
 	if value != memoryAfter {
-		t.Fatalf("Got the wrong value (%d != %d), set memory.limit_in_bytes failed", value, memoryAfter)
+		t.Fatal("Got the wrong value, set memory.limit_in_bytes failed.")
 	}
 	value, err = fscommon.GetCgroupParamUint(helper.CgroupPath, "memory.memsw.limit_in_bytes")
 	if err != nil {
 		t.Fatalf("Failed to parse memory.memsw.limit_in_bytes - %s", err)
 	}
 	if value != memoryswapAfter {
-		t.Fatalf("Got the wrong value (%d != %d), set memory.memsw.limit_in_bytes failed", value, memoryswapAfter)
+		t.Fatal("Got the wrong value, set memory.memsw.limit_in_bytes failed.")
 	}
 }
 
diff --git a/libcontainer/cgroups/utils_test.go b/libcontainer/cgroups/utils_test.go
index 0f621f25..f7ed4db1 100644
--- a/libcontainer/cgroups/utils_test.go
+++ b/libcontainer/cgroups/utils_test.go
@@ -642,3 +642,17 @@ func TestConvertMemorySwapToCgroupV2Value(t *testing.T) {
 		}
 	}
 }
+
+func TestConvertBlkIOToIOWeightValue(t *testing.T) {
+	cases := map[uint16]uint64{
+		0:    0,
+		10:   1,
+		1000: 10000,
+	}
+	for i, expected := range cases {
+		got := ConvertBlkIOToIOWeightValue(i)
+		if got != expected {
+			t.Errorf("expected ConvertBlkIOToIOWeightValue(%d) to be %d, got %d", i, expected, got)
+		}
+	}
+}
diff --git a/libcontainer/container_linux_test.go b/libcontainer/container_linux_test.go
index 2a9af6ad..73915997 100644
--- a/libcontainer/container_linux_test.go
+++ b/libcontainer/container_linux_test.go
@@ -55,10 +55,6 @@ func (m *mockCgroupManager) Exists() bool {
 	return err == nil
 }
 
-func (m *mockCgroupManager) OOMKillCount() (uint64, error) {
-	return 0, nil
-}
-
 func (m *mockCgroupManager) GetPaths() map[string]string {
 	return m.paths
 }
diff --git a/libcontainer/factory_linux_test.go b/libcontainer/factory_linux_test.go
index 665a8a7c..2b40e24d 100644
--- a/libcontainer/factory_linux_test.go
+++ b/libcontainer/factory_linux_test.go
@@ -180,7 +180,7 @@ func TestFactoryLoadContainer(t *testing.T) {
 			},
 		}
 	)
-	if err := os.Mkdir(filepath.Join(root, id), 0o700); err != nil {
+	if err := os.Mkdir(filepath.Join(root, id), 0700); err != nil {
 		t.Fatal(err)
 	}
 	if err := marshal(filepath.Join(root, id, stateFilename), expectedState); err != nil {
diff --git a/libcontainer/integration/checkpoint_test.go b/libcontainer/integration/checkpoint_test.go
index cecf0b54..627a013f 100644
--- a/libcontainer/integration/checkpoint_test.go
+++ b/libcontainer/integration/checkpoint_test.go
@@ -194,11 +194,10 @@ func testCheckpoint(t *testing.T, userns bool) {
 		t.Fatal(err)
 	}
 
-	var restoreStdout bytes.Buffer
 	restoreProcessConfig := &libcontainer.Process{
 		Cwd:    "/",
 		Stdin:  restoreStdinR,
-		Stdout: &restoreStdout,
+		Stdout: &stdout,
 		Init:   true,
 	}
 
@@ -243,7 +242,7 @@ func testCheckpoint(t *testing.T, userns bool) {
 		t.Fatal(s.String(), pid)
 	}
 
-	output := restoreStdout.String()
+	output := stdout.String()
 	if !strings.Contains(output, "Hello!") {
 		t.Fatal("Did not restore the pipe correctly:", output)
 	}
diff --git a/libcontainer/seccomp/patchbpf/enosys_linux_test.go b/libcontainer/seccomp/patchbpf/enosys_linux_test.go
index f9a4bf63..17b92af9 100644
--- a/libcontainer/seccomp/patchbpf/enosys_linux_test.go
+++ b/libcontainer/seccomp/patchbpf/enosys_linux_test.go
@@ -159,7 +159,7 @@ func testEnosysStub(t *testing.T, defaultAction configs.Action, arches []string)
 			type syscallTest struct {
 				syscall  string
 				sysno    libseccomp.ScmpSyscall
-				expected uint32
+				expected int
 			}
 
 			scmpArch, err := libseccomp.GetArchFromString(arch)
@@ -177,9 +177,9 @@ func testEnosysStub(t *testing.T, defaultAction configs.Action, arches []string)
 			// Add explicit syscalls (whether they will return -ENOSYS
 			// depends on the filter rules).
 			for idx, syscall := range explicitSyscalls {
-				expected := retFallthrough
+				expected := int(retFallthrough)
 				if idx >= enosysStart {
-					expected = retErrnoEnosys
+					expected = int(retErrnoEnosys)
 				}
 				sysno, err := libseccomp.GetSyscallFromNameByArch(syscall, scmpArch)
 				if err != nil {
@@ -201,7 +201,7 @@ func testEnosysStub(t *testing.T, defaultAction configs.Action, arches []string)
 				syscallTests = append(syscallTests, syscallTest{
 					sysno:    sysno,
 					syscall:  syscall,
-					expected: retFallthrough,
+					expected: int(retFallthrough),
 				})
 			}
 
@@ -216,7 +216,7 @@ func testEnosysStub(t *testing.T, defaultAction configs.Action, arches []string)
 				syscallTests = append(syscallTests, syscallTest{
 					sysno:    sysno,
 					syscall:  fmt.Sprintf("syscall_%#x", sysno),
-					expected: retErrnoEnosys,
+					expected: int(retErrnoEnosys),
 				})
 			}
 
@@ -224,17 +224,14 @@ func testEnosysStub(t *testing.T, defaultAction configs.Action, arches []string)
 			for _, test := range syscallTests {
 				// Override the expected value in the two special cases.
 				if !archSet[arch] || isAllowAction(defaultAction) {
-					test.expected = retFallthrough
+					test.expected = int(retFallthrough)
 				}
 
 				payload := mockSyscallPayload(t, test.sysno, nativeArch, 0x1337, 0xF00BA5)
-				// NOTE: golang.org/x/net/bpf returns int here rather
-				// than uint32.
-				rawRet, err := filter.Run(payload)
+				ret, err := filter.Run(payload)
 				if err != nil {
 					t.Fatalf("error running filter: %v", err)
 				}
-				ret := uint32(rawRet)
 				if ret != test.expected {
 					t.Logf("mock filter for %v %v:", arches, allowedSyscalls)
 					for idx, insn := range program {
diff --git a/tests/integration/README.md b/tests/integration/README.md
index 6d0201c1..5dcba002 100644
--- a/tests/integration/README.md
+++ b/tests/integration/README.md
@@ -49,7 +49,8 @@ $ ./install.sh /usr/local
 
 ## Writing integration tests
 
-[helper functions](https://github.com/opencontainers/runc/blob/master/tests/integration/helpers.bash)
+[helper functions]
+(https://github.com/opencontainers/runc/blob/master/tests/integration/helpers.bash)
 are provided in order to facilitate writing tests.
 
 ```sh
diff --git a/tests/integration/cgroups.bats b/tests/integration/cgroups.bats
index a3f19c03..d900f008 100644
--- a/tests/integration/cgroups.bats
+++ b/tests/integration/cgroups.bats
@@ -74,7 +74,7 @@ function setup() {
 
 	runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_permissions
 	[ "$status" -eq 1 ]
-	[[ "$output" == *"applying cgroup configuration"*"permission denied"* ]]
+	[[ ${lines[0]} == *"permission denied"* ]]
 }
 
 @test "runc create (rootless + limits + no cgrouppath + no permission) fails with informative error" {
@@ -87,8 +87,7 @@ function setup() {
 
 	runc run -d --console-socket "$CONSOLE_SOCKET" test_cgroups_permissions
 	[ "$status" -eq 1 ]
-	[[ "$output" == *"rootless needs no limits + no cgrouppath when no permission is granted for cgroups"* ]] ||
-		[[ "$output" == *"cannot set pids limit: container could not join or create cgroup"* ]]
+	[[ ${lines[0]} == *"rootless needs no limits + no cgrouppath when no permission is granted for cgroups"* ]] || [[ ${lines[0]} == *"cannot set pids limit: container could not join or create cgroup"* ]]
 }
 
 @test "runc create (limits + cgrouppath + permission on the cgroup dir) succeeds" {
@@ -196,8 +195,12 @@ function setup() {
 	[ "$status" -eq 0 ]
 
 	runc exec test_cgroups_unified sh -c 'cat /sys/fs/cgroup/io.bfq.weight'
-	[ "$status" -eq 0 ]
-	[ "$output" = 'default 750' ]
+	if [[ "$status" -eq 0 ]]; then
+		[ "$output" = 'default 750' ]
+	else
+		runc exec test_cgroups_unified sh -c 'cat /sys/fs/cgroup/io.weight'
+		[ "$output" = 'default 7475' ]
+	fi
 }
 
 @test "runc run (cgroup v2 resources.unified only)" {
diff --git a/tests/integration/update.bats b/tests/integration/update.bats
index bea67312..049d9587 100644
--- a/tests/integration/update.bats
+++ b/tests/integration/update.bats
@@ -235,42 +235,6 @@ EOF
 
 	check_cgroup_value "pids.max" 20
 	check_systemd_value "TasksMax" 20
-
-	if [ "$HAVE_SWAP" = "yes" ]; then
-		# Test case for https://github.com/opencontainers/runc/pull/592,
-		# checking libcontainer/cgroups/fs/memory.go:setMemoryAndSwap.
-
-		runc update test_update --memory 30M --memory-swap 50M
-		[ "$status" -eq 0 ]
-
-		check_cgroup_value $MEM_LIMIT $((30 * 1024 * 1024))
-		check_systemd_value $SD_MEM_LIMIT $((30 * 1024 * 1024))
-
-		if [ "$CGROUP_UNIFIED" = "yes" ]; then
-			# for cgroupv2, swap does not include mem
-			check_cgroup_value "$MEM_SWAP" $((20 * 1024 * 1024))
-			check_systemd_value "$SD_MEM_SWAP" $((20 * 1024 * 1024))
-		else
-			check_cgroup_value "$MEM_SWAP" $((50 * 1024 * 1024))
-			check_systemd_value "$SD_MEM_SWAP" $((50 * 1024 * 1024))
-		fi
-
-		# Now, set new memory to more than old swap
-		runc update test_update --memory 60M --memory-swap 80M
-		[ "$status" -eq 0 ]
-
-		check_cgroup_value $MEM_LIMIT $((60 * 1024 * 1024))
-		check_systemd_value $SD_MEM_LIMIT $((60 * 1024 * 1024))
-
-		if [ "$CGROUP_UNIFIED" = "yes" ]; then
-			# for cgroupv2, swap does not include mem
-			check_cgroup_value "$MEM_SWAP" $((20 * 1024 * 1024))
-			check_systemd_value "$SD_MEM_SWAP" $((20 * 1024 * 1024))
-		else
-			check_cgroup_value "$MEM_SWAP" $((80 * 1024 * 1024))
-			check_systemd_value "$SD_MEM_SWAP" $((80 * 1024 * 1024))
-		fi
-	fi
 }
 
 @test "update cgroup cpu limits" {