Repository: opencontainers/runc
Pull request #2292 (2020-04-03 13:51:55)

Add RDT Memory Bandwidth Monitoring (MBM) and Cache Monitoring Technology (CMT) statistics.
This pull request introduces the reading of Intel RDT Memory Bandwidth Monitoring statistics.

kolyshkin: @Creatone can you please avoid the merge commit? You can do so by using `git pull --rebase` instead of `git rebase`.

Patch:
diff --git a/.travis.yml b/.travis.yml
index 3c2e39b7..706a8cea 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,7 +18,7 @@ matrix:
       env:
         - RUNC_USE_SYSTEMD=1
       script:
-        - make all
+        - make BUILDTAGS="${BUILDTAGS}" all
         - sudo PATH="$PATH" make localintegration RUNC_USE_SYSTEMD=1
     - go: 1.13.x
       name: "cgroup-v2"
@@ -34,12 +34,9 @@ matrix:
         - sudo ssh default sudo podman build -t test /vagrant
         # Mounting /lib/modules into the container is necessary as CRIU wants to load (via iptables) additional modules
         - sudo ssh default sudo podman run --privileged --cgroupns=private -v /lib/modules:/lib/modules:ro test make localunittest
-        # cgroupv2+systemd: test on vagrant host itself as we need systemd
-        - sudo ssh default -t 'cd /vagrant && sudo make localintegration RUNC_USE_SYSTEMD=yes'
-        # same setup but with fs2 driver instead of systemd
-        - sudo ssh default -t 'cd /vagrant && sudo make localintegration'
   allow_failures:
     - go: tip
+    - name: "cgroup-v2"
 
 go_import_path: github.com/opencontainers/runc
 
@@ -48,6 +45,10 @@ sudo: required
 services:
   - docker
 
+env:
+  global:
+    - BUILDTAGS="seccomp apparmor selinux"
+
 before_install:
   - sudo apt-get -qq update
   - sudo apt-get install -y libseccomp-dev
@@ -57,5 +58,5 @@ before_install:
 
 script:
   - git-validation -run DCO,short-subject -v
-  - make
-  - make clean ci cross
+  - make BUILDTAGS="${BUILDTAGS}"
+  - make BUILDTAGS="${BUILDTAGS}" clean ci cross
diff --git a/Dockerfile b/Dockerfile
index dbebf238..94be151e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
 ARG GO_VERSION=1.13
-ARG BATS_VERSION=v1.1.0
+ARG BATS_VERSION=03608115df2071fff4eaaff1605768c275e5f81f
 ARG CRIU_VERSION=v3.13
 
 FROM golang:${GO_VERSION}-buster
@@ -51,11 +51,11 @@ RUN useradd -u1000 -m -d/home/rootless -s/bin/bash rootless
 # install bats
 ARG BATS_VERSION
 RUN cd /tmp \
-    && git clone https://github.com/bats-core/bats-core.git \
-    && cd bats-core \
+    && git clone https://github.com/sstephenson/bats.git \
+    && cd bats \
     && git reset --hard "${BATS_VERSION}" \
     && ./install.sh /usr/local \
-    && rm -rf /tmp/bats-core
+    && rm -rf /tmp/bats
 
 # install criu
 ARG CRIU_VERSION
diff --git a/MAINTAINERS b/MAINTAINERS
index acc3fe12..f8b008da 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4,4 +4,3 @@ Daniel, Dao Quang Minh <dqminh89@gmail.com> (@dqminh)
 Qiang Huang <h.huangqiang@huawei.com> (@hqhq)
 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 99d068c0..f21e3e0d 100644
--- a/Makefile
+++ b/Makefile
@@ -15,48 +15,40 @@ RUNC_IMAGE := runc_dev$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN))
 PROJECT := github.com/opencontainers/runc
 BUILDTAGS ?= seccomp selinux apparmor
 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 will be unsupported
-GO_BUILD := $(GO) build -mod=vendor -buildmode=pie $(EXTRA_FLAGS) -tags "$(BUILDTAGS)" \
-	-ldflags "-X main.gitCommit=$(COMMIT) -X main.version=$(VERSION) $(EXTRA_LDFLAGS)"
-GO_BUILD_STATIC := CGO_ENABLED=1 $(GO) build -mod=vendor $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo" \
-	-ldflags "-w -extldflags -static -X main.gitCommit=$(COMMIT) -X main.version=$(VERSION) $(EXTRA_LDFLAGS)"
+COMMIT ?= $(if $(shell git status --porcelain --untracked-files=no),"${COMMIT_NO}-dirty","${COMMIT_NO}")
 
 MAN_DIR := $(CURDIR)/man/man8
 MAN_PAGES = $(shell ls $(MAN_DIR)/*.8)
 MAN_PAGES_BASE = $(notdir $(MAN_PAGES))
-MAN_INSTALL_PATH := $(PREFIX)/share/man/man8/
+MAN_INSTALL_PATH := ${PREFIX}/share/man/man8/
 
 RELEASE_DIR := $(CURDIR)/release
 
+VERSION := ${shell cat ./VERSION}
+
 SHELL := $(shell command -v bash 2>/dev/null)
 
 .DEFAULT: runc
 
 runc: $(SOURCES)
-	$(GO_BUILD) -o runc .
+	$(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o runc .
 
 all: runc recvtty
 
 recvtty: contrib/cmd/recvtty/recvtty
 
 contrib/cmd/recvtty/recvtty: $(SOURCES)
-	$(GO_BUILD) -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
+	$(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
 
 static: $(SOURCES)
-	$(GO_BUILD_STATIC) -o runc .
-	$(GO_BUILD_STATIC) -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
+	CGO_ENABLED=1 $(GO) build $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo" -installsuffix netgo -ldflags "-w -extldflags -static -X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -o runc .
+	CGO_ENABLED=1 $(GO) build $(EXTRA_FLAGS) -tags "$(BUILDTAGS) netgo osusergo" -installsuffix netgo -ldflags "-w -extldflags -static -X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
 
 release:
 	script/release.sh -r release/$(VERSION) -v $(VERSION)
 
 dbuild: runcimage
-	$(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \
-		--privileged --rm \
-		-v $(CURDIR):/go/src/$(PROJECT) \
-		$(RUNC_IMAGE) make clean all
+	$(CONTAINER_ENGINE) run ${CONTAINER_ENGINE_RUN_FLAGS} --rm -v $(CURDIR):/go/src/$(PROJECT) --privileged $(RUNC_IMAGE) make clean all
 
 lint:
 	$(GO) vet $(allpackages)
@@ -66,7 +58,7 @@ man:
 	man/md2man-all.sh
 
 runcimage:
-	$(CONTAINER_ENGINE) build $(CONTAINER_ENGINE_BUILD_FLAGS) -t $(RUNC_IMAGE) .
+	$(CONTAINER_ENGINE) build ${CONTAINER_ENGINE_BUILD_FLAGS} -t $(RUNC_IMAGE) .
 
 test:
 	make unittest integration rootlessintegration
@@ -75,40 +67,25 @@ localtest:
 	make localunittest localintegration localrootlessintegration
 
 unittest: runcimage
-	$(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \
-		-t --privileged --rm \
-		-v /lib/modules:/lib/modules:ro \
-		-v $(CURDIR):/go/src/$(PROJECT) \
-		$(RUNC_IMAGE) make localunittest TESTFLAGS=$(TESTFLAGS)
+	$(CONTAINER_ENGINE) run ${CONTAINER_ENGINE_RUN_FLAGS} -t --privileged --rm -v /lib/modules:/lib/modules:ro -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localunittest TESTFLAGS=${TESTFLAGS}
 
 localunittest: all
-	$(GO) test -timeout 3m -tags "$(BUILDTAGS)" $(TESTFLAGS) -v $(allpackages)
+	$(GO) test -timeout 3m -tags "$(BUILDTAGS)" ${TESTFLAGS} -v $(allpackages)
 
 integration: runcimage
-	$(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \
-		-t --privileged --rm \
-		-v /lib/modules:/lib/modules:ro \
-		-v $(CURDIR):/go/src/$(PROJECT) \
-		$(RUNC_IMAGE) make localintegration TESTPATH=$(TESTPATH)
+	$(CONTAINER_ENGINE) run ${CONTAINER_ENGINE_RUN_FLAGS} -t --privileged --rm -v /lib/modules:/lib/modules:ro -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localintegration TESTPATH=${TESTPATH}
 
 localintegration: all
-	bats -t tests/integration$(TESTPATH)
+	bats -t tests/integration${TESTPATH}
 
 rootlessintegration: runcimage
-	$(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \
-		-t --privileged --rm \
-		-v $(CURDIR):/go/src/$(PROJECT) \
-		-e ROOTLESS_TESTPATH \
-		$(RUNC_IMAGE) make localrootlessintegration
+	$(CONTAINER_ENGINE) run -e ROOTLESS_TESTPATH ${CONTAINER_ENGINE_RUN_FLAGS} -t --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localrootlessintegration
 
 localrootlessintegration: all
 	tests/rootless.sh
 
 shell: runcimage
-	$(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \
-		-ti --privileged --rm \
-		-v $(CURDIR):/go/src/$(PROJECT) \
-		$(RUNC_IMAGE) bash
+	$(CONTAINER_ENGINE) run ${CONTAINER_ENGINE_RUN_FLAGS} -ti --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) bash
 
 install:
 	install -D -m0755 runc $(BINDIR)/runc
@@ -154,16 +131,13 @@ verify-dependencies: vendor
 		&& echo "all vendor files are up to date."
 
 cross: runcimage
-	$(CONTAINER_ENGINE) run $(CONTAINER_ENGINE_RUN_FLAGS) \
-		-e BUILDTAGS="$(BUILDTAGS)" --rm \
-		-v $(CURDIR):/go/src/$(PROJECT) \
-		$(RUNC_IMAGE) make localcross
+	$(CONTAINER_ENGINE) run ${CONTAINER_ENGINE_RUN_FLAGS} -e BUILDTAGS="$(BUILDTAGS)" --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) make localcross
 
 localcross:
-	CGO_ENABLED=1 GOARCH=arm GOARM=6 CC=arm-linux-gnueabi-gcc   $(GO_BUILD) -o runc-armel .
-	CGO_ENABLED=1 GOARCH=arm GOARM=7 CC=arm-linux-gnueabihf-gcc $(GO_BUILD) -o runc-armhf .
-	CGO_ENABLED=1 GOARCH=arm64 CC=aarch64-linux-gnu-gcc         $(GO_BUILD) -o runc-arm64 .
-	CGO_ENABLED=1 GOARCH=ppc64le CC=powerpc64le-linux-gnu-gcc   $(GO_BUILD) -o runc-ppc64le .
+	CGO_ENABLED=1 GOARCH=arm GOARM=6 CC=arm-linux-gnueabi-gcc $(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o runc-armel .
+	CGO_ENABLED=1 GOARCH=arm GOARM=7 CC=arm-linux-gnueabihf-gcc $(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o runc-armhf .
+	CGO_ENABLED=1 GOARCH=arm64 CC=aarch64-linux-gnu-gcc $(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o runc-arm64 .
+	CGO_ENABLED=1 GOARCH=ppc64le CC=powerpc64le-linux-gnu-gcc $(GO) build -buildmode=pie $(EXTRA_FLAGS) -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION} $(EXTRA_LDFLAGS)" -tags "$(BUILDTAGS)" -o runc-ppc64le .
 
 # memoize allpackages, so that it's executed only once and only if used
 _allpackages = $(shell $(GO) list ./... | grep -v vendor)
diff --git a/Vagrantfile b/Vagrantfile
index 4edeaaf2..165b0780 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -13,29 +13,6 @@ Vagrant.configure("2") do |config|
     v.cpus = 2
   end
   config.vm.provision "shell", inline: <<-SHELL
-    cat << EOF | dnf -y shell
-update
-install podman make golang-go libseccomp-devel bats jq \
- patch protobuf protobuf-c protobuf-c-devel protobuf-compiler \
- protobuf-devel protobuf-python libnl3-devel libcap-devel libnet-devel \
- nftables-devel libbsd-devel gnutls-devel
-ts run
-EOF
-    dnf clean all
-
-    # TODO: remove this after criu 3.14 is released
-    rpm -e --nodeps criu || true
-    CRIU_VERSION=v3.13
-    mkdir -p /usr/src/criu \
-        && curl -fsSL https://github.com/checkpoint-restore/criu/archive/${CRIU_VERSION}.tar.gz | tar -C /usr/src/criu/ -xz --strip-components=1 \
-        && cd /usr/src/criu \
-        && echo 1 > .gitid \
-        && curl -sSL https://github.com/checkpoint-restore/criu/commit/4c27b3db4f4325a311d8bfa9a50ea3efb4d6e377.patch | patch -p1 \
-        && curl -sSL https://github.com/checkpoint-restore/criu/commit/aac41164b2cd7f0d2047f207b32844524682e43f.patch | patch -p1 \
-        && curl -sSL https://github.com/checkpoint-restore/criu/commit/6f19249b2565f3f7c0a1f8f65b4ae180e8f7f34b.patch | patch -p1 \
-        && curl -sSL https://github.com/checkpoint-restore/criu/commit/378337a496ca759848180bc5411e4446298c5e4e.patch | patch -p1 \
-        && make install-criu \
-        && cd - \
-        && rm -rf /usr/src/criu
+    dnf install -y podman
   SHELL
 end
diff --git a/checkpoint.go b/checkpoint.go
index 228b6072..b0ddffde 100644
--- a/checkpoint.go
+++ b/checkpoint.go
@@ -63,8 +63,12 @@ checkpointed.`,
 			fatalf("Container cannot be checkpointed in %s state", status.String())
 		}
 		options := criuOptions(context)
-		if !(options.LeaveRunning || options.PreDump) {
-			// destroy container unless we tell CRIU to keep it
+		if !options.LeaveRunning || !options.PreDump {
+			// destroy prints out an error if we tell CRIU to
+			// leave the container running:
+			// ERRO[0000] container is not destroyed
+			// The message is correct, but we actually do not want
+			// to destroy the container in this case.
 			defer destroy(container)
 		}
 		// these are the mandatory criu options for a container
diff --git a/delete.go b/delete.go
index de8cd7ea..fb6f38ed 100644
--- a/delete.go
+++ b/delete.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"syscall"
 	"time"
 
 	"github.com/opencontainers/runc/libcontainer"
@@ -18,7 +19,7 @@ func killContainer(container libcontainer.Container) error {
 	_ = container.Signal(unix.SIGKILL, false)
 	for i := 0; i < 100; i++ {
 		time.Sleep(100 * time.Millisecond)
-		if err := container.Signal(unix.Signal(0), false); err != nil {
+		if err := container.Signal(syscall.Signal(0), false); err != nil {
 			destroy(container)
 			return nil
 		}
diff --git a/events.go b/events.go
index fb3f6302..fcda3b17 100644
--- a/events.go
+++ b/events.go
@@ -161,6 +161,12 @@ func convertLibcontainerStats(ls *libcontainer.Stats) *types.Stats {
 			s.IntelRdt.MemBwSchemaRoot = is.MemBwSchemaRoot
 			s.IntelRdt.MemBwSchema = is.MemBwSchema
 		}
+		if intelrdt.IsMBMEnabled() {
+			s.IntelRdt.MBMStats = is.MBMStats
+		}
+		if intelrdt.IsCMTEnabled() {
+			s.IntelRdt.CMTStats = is.CMTStats
+		}
 	}
 
 	s.NetworkInterfaces = ls.Interfaces
diff --git a/exec.go b/exec.go
index 6053ea97..b963d68e 100644
--- a/exec.go
+++ b/exec.go
@@ -203,7 +203,6 @@ func getProcess(context *cli.Context, bundle string) (*specs.Process, error) {
 	p.Env = append(p.Env, context.StringSlice("env")...)
 
 	// set the tty
-	p.Terminal = false
 	if context.IsSet("tty") {
 		p.Terminal = context.Bool("tty")
 	}
diff --git a/kill.go b/kill.go
index 46ea7003..25350f9f 100644
--- a/kill.go
+++ b/kill.go
@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"strconv"
 	"strings"
+	"syscall"
 
 	"github.com/urfave/cli"
 	"golang.org/x/sys/unix"
@@ -22,7 +23,7 @@ Where "<container-id>" is the name for the instance of the container and
 EXAMPLE:
 For example, if the container id is "ubuntu01" the following will send a "KILL"
 signal to the init process of the "ubuntu01" container:
-
+	 
        # runc kill ubuntu01 KILL`,
 	Flags: []cli.Flag{
 		cli.BoolFlag{
@@ -55,10 +56,10 @@ signal to the init process of the "ubuntu01" container:
 	},
 }
 
-func parseSignal(rawSignal string) (unix.Signal, error) {
+func parseSignal(rawSignal string) (syscall.Signal, error) {
 	s, err := strconv.Atoi(rawSignal)
 	if err == nil {
-		return unix.Signal(s), nil
+		return syscall.Signal(s), nil
 	}
 	sig := strings.ToUpper(rawSignal)
 	if !strings.HasPrefix(sig, "SIG") {
diff --git a/libcontainer/cgroups/fs/apply_raw.go b/libcontainer/cgroups/fs/apply_raw.go
index 59ce5ea6..df83d41c 100644
--- a/libcontainer/cgroups/fs/apply_raw.go
+++ b/libcontainer/cgroups/fs/apply_raw.go
@@ -8,6 +8,7 @@ import (
 	"os"
 	"path/filepath"
 	"sync"
+	"syscall"
 
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/configs"
@@ -117,7 +118,7 @@ func isIgnorableError(rootless bool, err error) bool {
 		return true
 	}
 	// Handle some specific syscall errors.
-	var errno unix.Errno
+	var errno syscall.Errno
 	if errors.As(err, &errno) {
 		return errno == unix.EROFS || errno == unix.EPERM || errno == unix.EACCES
 	}
diff --git a/libcontainer/cgroups/fs/memory.go b/libcontainer/cgroups/fs/memory.go
index 39809ef0..f81ed050 100644
--- a/libcontainer/cgroups/fs/memory.go
+++ b/libcontainer/cgroups/fs/memory.go
@@ -5,9 +5,7 @@ package fs
 import (
 	"bufio"
 	"fmt"
-	"math"
 	"os"
-	"path"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -18,16 +16,8 @@ import (
 )
 
 const (
-	numaNodeSymbol            = "N"
-	numaStatColumnSeparator   = " "
-	numaStatKeyValueSeparator = "="
-	numaStatMaxColumns        = math.MaxUint8 + 1
-	numaStatValueIndex        = 1
-	numaStatTypeIndex         = 0
-	numaStatColumnSliceLength = 2
-	cgroupMemorySwapLimit     = "memory.memsw.limit_in_bytes"
-	cgroupMemoryLimit         = "memory.limit_in_bytes"
-	cgroupMemoryPagesByNuma   = "memory.numa_stat"
+	cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes"
+	cgroupMemoryLimit     = "memory.limit_in_bytes"
 )
 
 type MemoryGroup struct {
@@ -219,13 +209,6 @@ func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
 	if value == 1 {
 		stats.MemoryStats.UseHierarchy = true
 	}
-
-	pagesByNUMA, err := getPageUsageByNUMA(path)
-	if err != nil {
-		return err
-	}
-	stats.MemoryStats.PageUsageByNUMA = pagesByNUMA
-
 	return nil
 }
 
@@ -286,77 +269,3 @@ func getMemoryData(path, name string) (cgroups.MemoryData, error) {
 
 	return memoryData, nil
 }
-
-func getPageUsageByNUMA(cgroupPath string) (cgroups.PageUsageByNUMA, error) {
-	stats := cgroups.PageUsageByNUMA{}
-
-	file, err := os.Open(path.Join(cgroupPath, cgroupMemoryPagesByNuma))
-	if err != nil {
-		return stats, err
-	}
-
-	scanner := bufio.NewScanner(file)
-	for scanner.Scan() {
-		var statsType string
-		statsByType := cgroups.PageStats{Nodes: map[uint8]uint64{}}
-		columns := strings.SplitN(scanner.Text(), numaStatColumnSeparator, numaStatMaxColumns)
-
-		for _, column := range columns {
-			pagesByNode := strings.SplitN(column, numaStatKeyValueSeparator, numaStatColumnSliceLength)
-
-			if strings.HasPrefix(pagesByNode[numaStatTypeIndex], numaNodeSymbol) {
-				nodeID, err := strconv.ParseUint(pagesByNode[numaStatTypeIndex][1:], 10, 8)
-				if err != nil {
-					return cgroups.PageUsageByNUMA{}, err
-				}
-
-				statsByType.Nodes[uint8(nodeID)], err = strconv.ParseUint(pagesByNode[numaStatValueIndex], 0, 64)
-				if err != nil {
-					return cgroups.PageUsageByNUMA{}, err
-				}
-			} else {
-				statsByType.Total, err = strconv.ParseUint(pagesByNode[numaStatValueIndex], 0, 64)
-				if err != nil {
-					return cgroups.PageUsageByNUMA{}, err
-				}
-
-				statsType = pagesByNode[numaStatTypeIndex]
-			}
-
-			err := addNUMAStatsByType(&stats, statsByType, statsType)
-			if err != nil {
-				return cgroups.PageUsageByNUMA{}, err
-			}
-		}
-	}
-	err = scanner.Err()
-	if err != nil {
-		return cgroups.PageUsageByNUMA{}, err
-	}
-
-	return stats, nil
-}
-
-func addNUMAStatsByType(stats *cgroups.PageUsageByNUMA, byTypeStats cgroups.PageStats, statsType string) error {
-	switch statsType {
-	case "total":
-		stats.Total = byTypeStats
-	case "file":
-		stats.File = byTypeStats
-	case "anon":
-		stats.Anon = byTypeStats
-	case "unevictable":
-		stats.Unevictable = byTypeStats
-	case "hierarchical_total":
-		stats.Hierarchical.Total = byTypeStats
-	case "hierarchical_file":
-		stats.Hierarchical.File = byTypeStats
-	case "hierarchical_anon":
-		stats.Hierarchical.Anon = byTypeStats
-	case "hierarchical_unevictable":
-		stats.Hierarchical.Unevictable = byTypeStats
-	default:
-		return fmt.Errorf("unsupported NUMA page type found: %s", statsType)
-	}
-	return nil
-}
diff --git a/libcontainer/cgroups/fs2/cpu.go b/libcontainer/cgroups/fs2/cpu.go
index 7129cf79..8ec85571 100644
--- a/libcontainer/cgroups/fs2/cpu.go
+++ b/libcontainer/cgroups/fs2/cpu.go
@@ -13,15 +13,7 @@ import (
 	"github.com/opencontainers/runc/libcontainer/configs"
 )
 
-func isCpuSet(cgroup *configs.Cgroup) bool {
-	return cgroup.Resources.CpuWeight != 0 || cgroup.Resources.CpuMax != ""
-}
-
 func setCpu(dirPath string, cgroup *configs.Cgroup) error {
-	if !isCpuSet(cgroup) {
-		return nil
-	}
-
 	// NOTE: .CpuShares is not used here. Conversion is the caller's responsibility.
 	if cgroup.Resources.CpuWeight != 0 {
 		if err := fscommon.WriteFile(dirPath, "cpu.weight", strconv.FormatUint(cgroup.Resources.CpuWeight, 10)); err != nil {
diff --git a/libcontainer/cgroups/fs2/cpuset.go b/libcontainer/cgroups/fs2/cpuset.go
index fb4456b4..6492ac93 100644
--- a/libcontainer/cgroups/fs2/cpuset.go
+++ b/libcontainer/cgroups/fs2/cpuset.go
@@ -7,15 +7,7 @@ import (
 	"github.com/opencontainers/runc/libcontainer/configs"
 )
 
-func isCpusetSet(cgroup *configs.Cgroup) bool {
-	return cgroup.Resources.CpusetCpus != "" || cgroup.Resources.CpusetMems != ""
-}
-
 func setCpuset(dirPath string, cgroup *configs.Cgroup) error {
-	if !isCpusetSet(cgroup) {
-		return nil
-	}
-
 	if cgroup.Resources.CpusetCpus != "" {
 		if err := fscommon.WriteFile(dirPath, "cpuset.cpus", cgroup.Resources.CpusetCpus); err != nil {
 			return err
diff --git a/libcontainer/cgroups/fs2/create.go b/libcontainer/cgroups/fs2/create.go
deleted file mode 100644
index 0cc2c1f6..00000000
--- a/libcontainer/cgroups/fs2/create.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package fs2
-
-import (
-	"fmt"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"github.com/opencontainers/runc/libcontainer/configs"
-)
-
-// neededControllers returns the string to write to cgroup.subtree_control,
-// containing the list of controllers to enable (for example, "+cpu +pids"),
-// based on (1) controllers available and (2) resources that are being set.
-//
-// The resulting string does not include "pseudo" controllers such as
-// "freezer" and "devices".
-func neededControllers(cgroup *configs.Cgroup) ([]string, error) {
-	var list []string
-
-	if cgroup == nil {
-		return list, nil
-	}
-
-	// list of all available controllers
-	const file = UnifiedMountpoint + "/cgroup.controllers"
-	content, err := ioutil.ReadFile(file)
-	if err != nil {
-		return list, err
-	}
-	avail := make(map[string]struct{})
-	for _, ctr := range strings.Fields(string(content)) {
-		avail[ctr] = struct{}{}
-	}
-
-	// add the controller if available
-	add := func(controller string) {
-		if _, ok := avail[controller]; ok {
-			list = append(list, "+"+controller)
-		}
-	}
-
-	if isPidsSet(cgroup) {
-		add("pids")
-	}
-	if isMemorySet(cgroup) {
-		add("memory")
-	}
-	if isIoSet(cgroup) {
-		add("io")
-	}
-	if isCpuSet(cgroup) {
-		add("cpu")
-	}
-	if isCpusetSet(cgroup) {
-		add("cpuset")
-	}
-	if isHugeTlbSet(cgroup) {
-		add("hugetlb")
-	}
-
-	return list, nil
-}
-
-// CreateCgroupPath creates cgroupv2 path, enabling all the
-// needed controllers in the process.
-func CreateCgroupPath(path string, c *configs.Cgroup) (Err error) {
-	if !strings.HasPrefix(path, UnifiedMountpoint) {
-		return fmt.Errorf("invalid cgroup path %s", path)
-	}
-
-	ctrs, err := neededControllers(c)
-	if err != nil {
-		return err
-	}
-	allCtrs := strings.Join(ctrs, " ")
-
-	elements := strings.Split(path, "/")
-	elements = elements[3:]
-	current := "/sys/fs"
-	for i, e := range elements {
-		current = filepath.Join(current, e)
-		if i > 0 {
-			if err := os.Mkdir(current, 0755); err != nil {
-				if !os.IsExist(err) {
-					return err
-				}
-			} else {
-				// If the directory was created, be sure it is not left around on errors.
-				current := current
-				defer func() {
-					if Err != nil {
-						os.Remove(current)
-					}
-				}()
-			}
-		}
-		// enable needed controllers
-		if i < len(elements)-1 {
-			file := filepath.Join(current, "cgroup.subtree_control")
-			if err := ioutil.WriteFile(file, []byte(allCtrs), 0755); err != nil {
-				// XXX: we can enable _some_ controllers doing it one-by one
-				// instead of erroring out -- does it makes sense to do so?
-				return err
-			}
-		}
-	}
-
-	return nil
-}
diff --git a/libcontainer/cgroups/fs2/defaultpath.go b/libcontainer/cgroups/fs2/defaultpath.go
index ab35e4b1..3d1fec33 100644
--- a/libcontainer/cgroups/fs2/defaultpath.go
+++ b/libcontainer/cgroups/fs2/defaultpath.go
@@ -48,11 +48,6 @@ func defaultDirPath(c *configs.Cgroup) (string, error) {
 	if err != nil {
 		return "", err
 	}
-	// The current user scope most probably has tasks in it already,
-	// making it impossible to enable controllers for its sub-cgroup.
-	// A parent cgroup (with no tasks in it) is what we need.
-	ownCgroup = filepath.Dir(ownCgroup)
-
 	return _defaultDirPath(UnifiedMountpoint, cgPath, cgParent, cgName, ownCgroup)
 }
 
diff --git a/libcontainer/cgroups/fs2/devices.go b/libcontainer/cgroups/fs2/devices.go
index 6e45c787..e0fd6854 100644
--- a/libcontainer/cgroups/fs2/devices.go
+++ b/libcontainer/cgroups/fs2/devices.go
@@ -40,15 +40,11 @@ func canSkipEBPFError(cgroup *configs.Cgroup) bool {
 
 func setDevices(dirPath string, cgroup *configs.Cgroup) error {
 	devices := cgroup.Devices
-	// never set by OCI specconv
 	if allowAllDevices := cgroup.Resources.AllowAllDevices; allowAllDevices != nil {
+		// never set by OCI specconv, but *allowAllDevices=false is still used by the integration test
 		if *allowAllDevices == true {
-			if len(cgroup.Resources.DeniedDevices) != 0 {
-				return errors.New("libcontainer: can't use DeniedDevices together with AllowAllDevices")
-			}
-			return nil
+			return errors.New("libcontainer AllowAllDevices is not supported, use Devices")
 		}
-		// *allowAllDevices=false is still used by the integration test
 		for _, ad := range cgroup.Resources.AllowedDevices {
 			d := *ad
 			d.Allow = true
diff --git a/libcontainer/cgroups/fs2/fs2.go b/libcontainer/cgroups/fs2/fs2.go
index 93a2aa59..4e0cb6fa 100644
--- a/libcontainer/cgroups/fs2/fs2.go
+++ b/libcontainer/cgroups/fs2/fs2.go
@@ -8,21 +8,12 @@ import (
 	"path/filepath"
 	"strings"
 
+	securejoin "github.com/cyphar/filepath-securejoin"
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/configs"
 	"github.com/pkg/errors"
 )
 
-type manager struct {
-	config *configs.Cgroup
-	// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
-	dirPath string
-	// controllers is content of "cgroup.controllers" file.
-	// excludes pseudo-controllers ("devices" and "freezer").
-	controllers map[string]struct{}
-	rootless    bool
-}
-
 // NewManager creates a manager for cgroup v2 unified hierarchy.
 // dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope".
 // If dirPath is empty, it is automatically set using config.
@@ -30,45 +21,62 @@ func NewManager(config *configs.Cgroup, dirPath string, rootless bool) (cgroups.
 	if config == nil {
 		config = &configs.Cgroup{}
 	}
-	if dirPath == "" {
+	if dirPath != "" {
+		if filepath.Clean(dirPath) != dirPath || !filepath.IsAbs(dirPath) {
+			return nil, errors.Errorf("invalid dir path %q", dirPath)
+		}
+	} else {
 		var err error
 		dirPath, err = defaultDirPath(config)
 		if err != nil {
 			return nil, err
 		}
 	}
+	controllers, err := detectControllers(dirPath)
+	if err != nil && !rootless {
+		return nil, err
+	}
 
 	m := &manager{
-		config:   config,
-		dirPath:  dirPath,
-		rootless: rootless,
+		config:      config,
+		dirPath:     dirPath,
+		controllers: controllers,
+		rootless:    rootless,
 	}
 	return m, nil
 }
 
-func (m *manager) getControllers() error {
-	if m.controllers != nil {
-		return nil
+func detectControllers(dirPath string) (map[string]struct{}, error) {
+	if err := os.MkdirAll(dirPath, 0755); err != nil {
+		return nil, err
 	}
-
-	file := filepath.Join(m.dirPath, "cgroup.controllers")
-	data, err := ioutil.ReadFile(file)
-	if err != nil && !m.rootless {
-		return err
+	controllersPath, err := securejoin.SecureJoin(dirPath, "cgroup.controllers")
+	if err != nil {
+		return nil, err
 	}
-	fields := strings.Fields(string(data))
-	m.controllers = make(map[string]struct{}, len(fields))
-	for _, c := range fields {
-		m.controllers[c] = struct{}{}
+	controllersData, err := ioutil.ReadFile(controllersPath)
+	if err != nil {
+		return nil, err
 	}
+	controllersFields := strings.Fields(string(controllersData))
+	controllers := make(map[string]struct{}, len(controllersFields))
+	for _, c := range controllersFields {
+		controllers[c] = struct{}{}
+	}
+	return controllers, nil
+}
 
-	return nil
+type manager struct {
+	config *configs.Cgroup
+	// dirPath is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
+	dirPath string
+	// controllers is content of "cgroup.controllers" file.
+	// excludes pseudo-controllers ("devices" and "freezer").
+	controllers map[string]struct{}
+	rootless    bool
 }
 
 func (m *manager) Apply(pid int) error {
-	if err := CreateCgroupPath(m.dirPath, m.config); err != nil {
-		return err
-	}
 	if err := cgroups.WriteCgroupProc(m.dirPath, pid); err != nil && !m.rootless {
 		return err
 	}
@@ -89,9 +97,6 @@ func (m *manager) GetStats() (*cgroups.Stats, error) {
 	)
 
 	st := cgroups.NewStats()
-	if err := m.getControllers(); err != nil {
-		return st, err
-	}
 
 	// pids (since kernel 4.5)
 	if _, ok := m.controllers["pids"]; ok {
@@ -142,15 +147,11 @@ func (m *manager) Freeze(state configs.FreezerState) error {
 }
 
 func (m *manager) Destroy() error {
-	if err := os.Remove(m.dirPath); err != nil && !os.IsNotExist(err) {
-		return err
-	}
-	return nil
+	return os.RemoveAll(m.dirPath)
 }
 
 // GetPaths is for compatibility purpose and should be removed in future
 func (m *manager) GetPaths() map[string]string {
-	_ = m.getControllers()
 	paths := map[string]string{
 		// pseudo-controller for compatibility
 		"devices": m.dirPath,
@@ -170,9 +171,6 @@ func (m *manager) Set(container *configs.Config) error {
 	if container == nil || container.Cgroups == nil {
 		return nil
 	}
-	if err := m.getControllers(); err != nil {
-		return err
-	}
 	var errs []error
 	// pids (since kernel 4.5)
 	if _, ok := m.controllers["pids"]; ok {
diff --git a/libcontainer/cgroups/fs2/hugetlb.go b/libcontainer/cgroups/fs2/hugetlb.go
index 4a399aae..c7610c0a 100644
--- a/libcontainer/cgroups/fs2/hugetlb.go
+++ b/libcontainer/cgroups/fs2/hugetlb.go
@@ -15,14 +15,7 @@ import (
 	"github.com/opencontainers/runc/libcontainer/configs"
 )
 
-func isHugeTlbSet(cgroup *configs.Cgroup) bool {
-	return len(cgroup.Resources.HugetlbLimit) > 0
-}
-
 func setHugeTlb(dirPath string, cgroup *configs.Cgroup) error {
-	if !isHugeTlbSet(cgroup) {
-		return nil
-	}
 	for _, hugetlb := range cgroup.Resources.HugetlbLimit {
 		if err := fscommon.WriteFile(dirPath, strings.Join([]string{"hugetlb", hugetlb.Pagesize, "max"}, "."), strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
 			return err
diff --git a/libcontainer/cgroups/fs2/io.go b/libcontainer/cgroups/fs2/io.go
index bbe3ac06..677c8d28 100644
--- a/libcontainer/cgroups/fs2/io.go
+++ b/libcontainer/cgroups/fs2/io.go
@@ -14,19 +14,7 @@ import (
 	"github.com/opencontainers/runc/libcontainer/configs"
 )
 
-func isIoSet(cgroup *configs.Cgroup) bool {
-	return cgroup.Resources.BlkioWeight != 0 ||
-		len(cgroup.Resources.BlkioThrottleReadBpsDevice) > 0 ||
-		len(cgroup.Resources.BlkioThrottleWriteBpsDevice) > 0 ||
-		len(cgroup.Resources.BlkioThrottleReadIOPSDevice) > 0 ||
-		len(cgroup.Resources.BlkioThrottleWriteIOPSDevice) > 0
-}
-
 func setIo(dirPath string, cgroup *configs.Cgroup) error {
-	if !isIoSet(cgroup) {
-		return nil
-	}
-
 	if cgroup.Resources.BlkioWeight != 0 {
 		filename := "io.bfq.weight"
 		if err := fscommon.WriteFile(dirPath, filename,
diff --git a/libcontainer/cgroups/fs2/memory.go b/libcontainer/cgroups/fs2/memory.go
index 681bedd1..09a92d46 100644
--- a/libcontainer/cgroups/fs2/memory.go
+++ b/libcontainer/cgroups/fs2/memory.go
@@ -32,15 +32,7 @@ func numToStr(value int64) (ret string) {
 	return ret
 }
 
-func isMemorySet(cgroup *configs.Cgroup) bool {
-	return cgroup.Resources.MemoryReservation != 0 ||
-		cgroup.Resources.Memory != 0 || cgroup.Resources.MemorySwap != 0
-}
-
 func setMemory(dirPath string, cgroup *configs.Cgroup) error {
-	if !isMemorySet(cgroup) {
-		return nil
-	}
 	swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(cgroup.Resources.MemorySwap, cgroup.Resources.Memory)
 	if err != nil {
 		return err
diff --git a/libcontainer/cgroups/fs2/pids.go b/libcontainer/cgroups/fs2/pids.go
index b8153d28..0cf860f9 100644
--- a/libcontainer/cgroups/fs2/pids.go
+++ b/libcontainer/cgroups/fs2/pids.go
@@ -14,14 +14,7 @@ import (
 	"golang.org/x/sys/unix"
 )
 
-func isPidsSet(cgroup *configs.Cgroup) bool {
-	return cgroup.Resources.PidsLimit != 0
-}
-
 func setPids(dirPath string, cgroup *configs.Cgroup) error {
-	if !isPidsSet(cgroup) {
-		return nil
-	}
 	if val := numToStr(cgroup.Resources.PidsLimit); val != "" {
 		if err := fscommon.WriteFile(dirPath, "pids.max", val); err != nil {
 			return err
diff --git a/libcontainer/cgroups/fscommon/fscommon.go b/libcontainer/cgroups/fscommon/fscommon.go
index 46e06dc6..dc53987e 100644
--- a/libcontainer/cgroups/fscommon/fscommon.go
+++ b/libcontainer/cgroups/fscommon/fscommon.go
@@ -5,11 +5,11 @@ package fscommon
 import (
 	"io/ioutil"
 	"os"
+	"syscall"
 
 	securejoin "github.com/cyphar/filepath-securejoin"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
-	"golang.org/x/sys/unix"
 )
 
 func WriteFile(dir, file, data string) error {
@@ -41,10 +41,20 @@ func ReadFile(dir, file string) (string, error) {
 func retryingWriteFile(filename string, data []byte, perm os.FileMode) error {
 	for {
 		err := ioutil.WriteFile(filename, data, perm)
-		if errors.Is(err, unix.EINTR) {
+		if isInterruptedWriteFile(err) {
 			logrus.Infof("interrupted while writing %s to %s", string(data), filename)
 			continue
 		}
 		return err
 	}
 }
+
+func isInterruptedWriteFile(err error) bool {
+	if patherr, ok := err.(*os.PathError); ok {
+		errno, ok2 := patherr.Err.(syscall.Errno)
+		if ok2 && errno == syscall.EINTR {
+			return true
+		}
+	}
+	return false
+}
diff --git a/libcontainer/cgroups/stats.go b/libcontainer/cgroups/stats.go
index eaf8f234..8eeedc55 100644
--- a/libcontainer/cgroups/stats.go
+++ b/libcontainer/cgroups/stats.go
@@ -51,33 +51,12 @@ type MemoryStats struct {
 	KernelUsage MemoryData `json:"kernel_usage,omitempty"`
 	// usage of kernel TCP memory
 	KernelTCPUsage MemoryData `json:"kernel_tcp_usage,omitempty"`
-	// usage of memory pages by NUMA node
-	// see chapter 5.6 of memory controller documentation
-	PageUsageByNUMA PageUsageByNUMA `json:"page_usage_by_numa,omitempty"`
 	// if true, memory usage is accounted for throughout a hierarchy of cgroups.
 	UseHierarchy bool `json:"use_hierarchy"`
 
 	Stats map[string]uint64 `json:"stats,omitempty"`
 }
 
-type PageUsageByNUMA struct {
-	// Embedding is used as types can't be recursive.
-	PageUsageByNUMAInner
-	Hierarchical PageUsageByNUMAInner `json:"hierarchical,omitempty"`
-}
-
-type PageUsageByNUMAInner struct {
-	Total       PageStats `json:"total,omitempty"`
-	File        PageStats `json:"file,omitempty"`
-	Anon        PageStats `json:"anon,omitempty"`
-	Unevictable PageStats `json:"unevictable,omitempty"`
-}
-
-type PageStats struct {
-	Total uint64           `json:"total,omitempty"`
-	Nodes map[uint8]uint64 `json:"nodes,omitempty"`
-}
-
 type PidsStats struct {
 	// number of pids in the cgroup
 	Current uint64 `json:"current,omitempty"`
diff --git a/libcontainer/cgroups/systemd/unsupported.go b/libcontainer/cgroups/systemd/apply_nosystemd.go
similarity index 100%
rename from libcontainer/cgroups/systemd/unsupported.go
rename to libcontainer/cgroups/systemd/apply_nosystemd.go
diff --git a/libcontainer/cgroups/systemd/v1.go b/libcontainer/cgroups/systemd/apply_systemd.go
similarity index 77%
rename from libcontainer/cgroups/systemd/v1.go
rename to libcontainer/cgroups/systemd/apply_systemd.go
index af3ca798..4878448d 100644
--- a/libcontainer/cgroups/systemd/v1.go
+++ b/libcontainer/cgroups/systemd/apply_systemd.go
@@ -4,6 +4,7 @@ package systemd
 
 import (
 	"errors"
+	"fmt"
 	"io/ioutil"
 	"math"
 	"os"
@@ -13,6 +14,7 @@ import (
 	"time"
 
 	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
+	dbus "github.com/godbus/dbus/v5"
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/cgroups/fs"
 	"github.com/opencontainers/runc/libcontainer/configs"
@@ -63,57 +65,61 @@ var legacySubsystems = subsystemSet{
 	&fs.NameGroup{GroupName: "name=systemd"},
 }
 
-func genV1ResourcesProperties(c *configs.Cgroup) ([]systemdDbus.Property, error) {
-	var properties []systemdDbus.Property
-	if c.Resources.Memory != 0 {
-		properties = append(properties,
-			newProp("MemoryLimit", uint64(c.Resources.Memory)))
-	}
+var (
+	connOnce sync.Once
+	connDbus *systemdDbus.Conn
+	connErr  error
+)
 
-	if c.Resources.CpuShares != 0 {
-		properties = append(properties,
-			newProp("CPUShares", c.Resources.CpuShares))
+func newProp(name string, units interface{}) systemdDbus.Property {
+	return systemdDbus.Property{
+		Name:  name,
+		Value: dbus.MakeVariant(units),
 	}
+}
 
-	// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
-	if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
-		// corresponds to USEC_INFINITY in systemd
-		// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
-		// always setting a property value ensures we can apply a quota and remove it later
-		cpuQuotaPerSecUSec := uint64(math.MaxUint64)
-		if c.Resources.CpuQuota > 0 {
-			// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
-			// (integer percentage of CPU) internally.  This means that if a fractional percent of
-			// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
-			// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
-			cpuQuotaPerSecUSec = uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
-			if cpuQuotaPerSecUSec%10000 != 0 {
-				cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
-			}
-		}
-		properties = append(properties,
-			newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
+// NOTE: This function comes from package github.com/coreos/go-systemd/util
+// It was borrowed here to avoid a dependency on cgo.
+//
+// IsRunningSystemd checks whether the host was booted with systemd as its init
+// system. This functions similarly to systemd's `sd_booted(3)`: internally, it
+// checks whether /run/systemd/system/ exists and is a directory.
+// http://www.freedesktop.org/software/systemd/man/sd_booted.html
+func IsRunningSystemd() bool {
+	fi, err := os.Lstat("/run/systemd/system")
+	if err != nil {
+		return false
 	}
+	return fi.IsDir()
+}
 
-	if c.Resources.BlkioWeight != 0 {
-		properties = append(properties,
-			newProp("BlockIOWeight", uint64(c.Resources.BlkioWeight)))
-	}
+// getDbusConnection lazy initializes systemd dbus connection
+// and returns it
+func getDbusConnection() (*systemdDbus.Conn, error) {
+	connOnce.Do(func() {
+		connDbus, connErr = systemdDbus.New()
+	})
+	return connDbus, connErr
+}
 
-	if c.Resources.PidsLimit > 0 {
-		properties = append(properties,
-			newProp("TasksAccounting", true),
-			newProp("TasksMax", uint64(c.Resources.PidsLimit)))
+func NewSystemdCgroupsManager() (func(config *configs.Cgroup, paths map[string]string) cgroups.Manager, error) {
+	if !IsRunningSystemd() {
+		return nil, fmt.Errorf("systemd not running on this host, can't use systemd as a cgroups.Manager")
 	}
-
-	// We have to set kernel memory here, as we can't change it once
-	// processes have been attached to the cgroup.
-	if c.Resources.KernelMemory != 0 {
-		if err := setKernelMemory(c); err != nil {
-			return nil, err
-		}
+	if cgroups.IsCgroup2UnifiedMode() {
+		return func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
+			return &UnifiedManager{
+				Cgroups: config,
+				Paths:   paths,
+			}
+		}, nil
 	}
-	return properties, nil
+	return func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
+		return &LegacyManager{
+			Cgroups: config,
+			Paths:   paths,
+		}
+	}, nil
 }
 
 func (m *LegacyManager) Apply(pid int) error {
@@ -177,11 +183,55 @@ func (m *LegacyManager) Apply(pid int) error {
 	properties = append(properties,
 		newProp("DefaultDependencies", false))
 
-	resourcesProperties, err := genV1ResourcesProperties(c)
-	if err != nil {
-		return err
+	if c.Resources.Memory != 0 {
+		properties = append(properties,
+			newProp("MemoryLimit", uint64(c.Resources.Memory)))
+	}
+
+	if c.Resources.CpuShares != 0 {
+		properties = append(properties,
+			newProp("CPUShares", c.Resources.CpuShares))
 	}
-	properties = append(properties, resourcesProperties...)
+
+	// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
+	if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
+		// corresponds to USEC_INFINITY in systemd
+		// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
+		// always setting a property value ensures we can apply a quota and remove it later
+		cpuQuotaPerSecUSec := uint64(math.MaxUint64)
+		if c.Resources.CpuQuota > 0 {
+			// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
+			// (integer percentage of CPU) internally.  This means that if a fractional percent of
+			// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
+			// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
+			cpuQuotaPerSecUSec = uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
+			if cpuQuotaPerSecUSec%10000 != 0 {
+				cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
+			}
+		}
+		properties = append(properties,
+			newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
+	}
+
+	if c.Resources.BlkioWeight != 0 {
+		properties = append(properties,
+			newProp("BlockIOWeight", uint64(c.Resources.BlkioWeight)))
+	}
+
+	if c.Resources.PidsLimit > 0 {
+		properties = append(properties,
+			newProp("TasksAccounting", true),
+			newProp("TasksMax", uint64(c.Resources.PidsLimit)))
+	}
+
+	// We have to set kernel memory here, as we can't change it once
+	// processes have been attached to the cgroup.
+	if c.Resources.KernelMemory != 0 {
+		if err := setKernelMemory(c); err != nil {
+			return err
+		}
+	}
+
 	properties = append(properties, c.SystemdProps...)
 
 	dbusConnection, err := getDbusConnection()
@@ -301,6 +351,40 @@ func joinCgroups(c *configs.Cgroup, pid int) error {
 	return nil
 }
 
+// systemd represents slice hierarchy using `-`, so we need to follow suit when
+// generating the path of slice. Essentially, test-a-b.slice becomes
+// /test.slice/test-a.slice/test-a-b.slice.
+func ExpandSlice(slice string) (string, error) {
+	suffix := ".slice"
+	// Name has to end with ".slice", but can't be just ".slice".
+	if len(slice) < len(suffix) || !strings.HasSuffix(slice, suffix) {
+		return "", fmt.Errorf("invalid slice name: %s", slice)
+	}
+
+	// Path-separators are not allowed.
+	if strings.Contains(slice, "/") {
+		return "", fmt.Errorf("invalid slice name: %s", slice)
+	}
+
+	var path, prefix string
+	sliceName := strings.TrimSuffix(slice, suffix)
+	// if input was -.slice, we should just return root now
+	if sliceName == "-" {
+		return "/", nil
+	}
+	for _, component := range strings.Split(sliceName, "-") {
+		// test--a.slice isn't permitted, nor is -test.slice.
+		if component == "" {
+			return "", fmt.Errorf("invalid slice name: %s", slice)
+		}
+
+		// Append the component to the path and to the prefix.
+		path += "/" + prefix + component + suffix
+		prefix += component + "-"
+	}
+	return path, nil
+}
+
 func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) {
 	mountpoint, err := cgroups.FindCgroupMountpoint(c.Path, subsystem)
 	if err != nil {
@@ -385,18 +469,6 @@ func (m *LegacyManager) Set(container *configs.Config) error {
 	if m.Cgroups.Paths != nil {
 		return nil
 	}
-	properties, err := genV1ResourcesProperties(container.Cgroups)
-	if err != nil {
-		return err
-	}
-	dbusConnection, err := getDbusConnection()
-	if err != nil {
-		return err
-	}
-	if err := dbusConnection.SetUnitProperties(getUnitName(container.Cgroups), true, properties...); err != nil {
-		return err
-	}
-
 	for _, sys := range legacySubsystems {
 		// Get the subsystem path, but don't error out for not found cgroups.
 		path, err := getSubsystemPath(container.Cgroups, sys.Name())
@@ -417,6 +489,14 @@ func (m *LegacyManager) Set(container *configs.Config) error {
 	return nil
 }
 
+func getUnitName(c *configs.Cgroup) string {
+	// by default, we create a scope unless the user explicitly asks for a slice.
+	if !strings.HasSuffix(c.Name, ".slice") {
+		return fmt.Sprintf("%s-%s.scope", c.ScopePrefix, c.Name)
+	}
+	return c.Name
+}
+
 func setKernelMemory(c *configs.Cgroup) error {
 	path, err := getSubsystemPath(c, "memory")
 	if err != nil && !cgroups.IsNotFound(err) {
@@ -437,6 +517,17 @@ func setKernelMemory(c *configs.Cgroup) error {
 	}
 	return fs.EnableKernelMemoryAccounting(path)
 }
+
+// isUnitExists returns true if the error is that a systemd unit already exists.
+func isUnitExists(err error) bool {
+	if err != nil {
+		if dbusError, ok := err.(dbus.Error); ok {
+			return strings.Contains(dbusError.Name, "org.freedesktop.systemd1.UnitExists")
+		}
+	}
+	return false
+}
+
 func (m *LegacyManager) GetCgroups() (*configs.Cgroup, error) {
 	return m.Cgroups, nil
 }
diff --git a/libcontainer/cgroups/systemd/common.go b/libcontainer/cgroups/systemd/common.go
deleted file mode 100644
index eeb0d532..00000000
--- a/libcontainer/cgroups/systemd/common.go
+++ /dev/null
@@ -1,101 +0,0 @@
-package systemd
-
-import (
-	"fmt"
-	"os"
-	"strings"
-	"sync"
-
-	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
-	dbus "github.com/godbus/dbus/v5"
-	"github.com/opencontainers/runc/libcontainer/configs"
-)
-
-var (
-	connOnce sync.Once
-	connDbus *systemdDbus.Conn
-	connErr  error
-)
-
-// NOTE: This function comes from package github.com/coreos/go-systemd/util
-// It was borrowed here to avoid a dependency on cgo.
-//
-// IsRunningSystemd checks whether the host was booted with systemd as its init
-// system. This functions similarly to systemd's `sd_booted(3)`: internally, it
-// checks whether /run/systemd/system/ exists and is a directory.
-// http://www.freedesktop.org/software/systemd/man/sd_booted.html
-func IsRunningSystemd() bool {
-	fi, err := os.Lstat("/run/systemd/system")
-	if err != nil {
-		return false
-	}
-	return fi.IsDir()
-}
-
-// systemd represents slice hierarchy using `-`, so we need to follow suit when
-// generating the path of slice. Essentially, test-a-b.slice becomes
-// /test.slice/test-a.slice/test-a-b.slice.
-func ExpandSlice(slice string) (string, error) {
-	suffix := ".slice"
-	// Name has to end with ".slice", but can't be just ".slice".
-	if len(slice) < len(suffix) || !strings.HasSuffix(slice, suffix) {
-		return "", fmt.Errorf("invalid slice name: %s", slice)
-	}
-
-	// Path-separators are not allowed.
-	if strings.Contains(slice, "/") {
-		return "", fmt.Errorf("invalid slice name: %s", slice)
-	}
-
-	var path, prefix string
-	sliceName := strings.TrimSuffix(slice, suffix)
-	// if input was -.slice, we should just return root now
-	if sliceName == "-" {
-		return "/", nil
-	}
-	for _, component := range strings.Split(sliceName, "-") {
-		// test--a.slice isn't permitted, nor is -test.slice.
-		if component == "" {
-			return "", fmt.Errorf("invalid slice name: %s", slice)
-		}
-
-		// Append the component to the path and to the prefix.
-		path += "/" + prefix + component + suffix
-		prefix += component + "-"
-	}
-	return path, nil
-}
-
-// getDbusConnection lazy initializes systemd dbus connection
-// and returns it
-func getDbusConnection() (*systemdDbus.Conn, error) {
-	connOnce.Do(func() {
-		connDbus, connErr = systemdDbus.New()
-	})
-	return connDbus, connErr
-}
-
-func newProp(name string, units interface{}) systemdDbus.Property {
-	return systemdDbus.Property{
-		Name:  name,
-		Value: dbus.MakeVariant(units),
-	}
-}
-
-func getUnitName(c *configs.Cgroup) string {
-	// by default, we create a scope unless the user explicitly asks for a slice.
-	if !strings.HasSuffix(c.Name, ".slice") {
-		return fmt.Sprintf("%s-%s.scope", c.ScopePrefix, c.Name)
-	}
-	return c.Name
-}
-
-// isUnitExists returns true if the error is that a systemd unit already exists.
-func isUnitExists(err error) bool {
-	if err != nil {
-		if dbusError, ok := err.(dbus.Error); ok {
-			return strings.Contains(dbusError.Name, "org.freedesktop.systemd1.UnitExists")
-		}
-	}
-	return false
-}
diff --git a/libcontainer/cgroups/systemd/v2.go b/libcontainer/cgroups/systemd/unified_hierarchy.go
similarity index 64%
rename from libcontainer/cgroups/systemd/v2.go
rename to libcontainer/cgroups/systemd/unified_hierarchy.go
index 3a8adfd9..2dffc34e 100644
--- a/libcontainer/cgroups/systemd/v2.go
+++ b/libcontainer/cgroups/systemd/unified_hierarchy.go
@@ -3,6 +3,8 @@
 package systemd
 
 import (
+	"bytes"
+	"io/ioutil"
 	"math"
 	"os"
 	"path/filepath"
@@ -11,92 +13,34 @@ import (
 	"time"
 
 	systemdDbus "github.com/coreos/go-systemd/v22/dbus"
-	securejoin "github.com/cyphar/filepath-securejoin"
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
 	"github.com/opencontainers/runc/libcontainer/configs"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
 
-type unifiedManager struct {
+type UnifiedManager struct {
 	mu      sync.Mutex
-	cgroups *configs.Cgroup
-	// path is like "/sys/fs/cgroup/user.slice/user-1001.slice/session-1.scope"
-	path     string
-	rootless bool
+	Cgroups *configs.Cgroup
+	Paths   map[string]string
 }
 
-func NewUnifiedManager(config *configs.Cgroup, path string, rootless bool) *unifiedManager {
-	return &unifiedManager{
-		cgroups:  config,
-		path:     path,
-		rootless: rootless,
-	}
-}
-
-func genV2ResourcesProperties(c *configs.Cgroup) ([]systemdDbus.Property, error) {
-	var properties []systemdDbus.Property
-
-	if c.Resources.Memory != 0 {
-		properties = append(properties,
-			newProp("MemoryMax", uint64(c.Resources.Memory)))
-	}
-
-	swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(c.Resources.MemorySwap, c.Resources.Memory)
-	if err != nil {
-		return nil, err
-	}
-	if swap > 0 {
-		properties = append(properties,
-			newProp("MemorySwapMax", uint64(swap)))
-	}
-
-	if c.Resources.CpuWeight != 0 {
-		properties = append(properties,
-			newProp("CPUWeight", c.Resources.CpuWeight))
-	}
-
-	// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
-	if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
-		// corresponds to USEC_INFINITY in systemd
-		// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
-		// always setting a property value ensures we can apply a quota and remove it later
-		cpuQuotaPerSecUSec := uint64(math.MaxUint64)
-		if c.Resources.CpuQuota > 0 {
-			// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
-			// (integer percentage of CPU) internally.  This means that if a fractional percent of
-			// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
-			// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
-			cpuQuotaPerSecUSec = uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
-			if cpuQuotaPerSecUSec%10000 != 0 {
-				cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
-			}
-		}
-		properties = append(properties,
-			newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
-	}
-
-	if c.Resources.PidsLimit > 0 {
-		properties = append(properties,
-			newProp("TasksAccounting", true),
-			newProp("TasksMax", uint64(c.Resources.PidsLimit)))
-	}
-
-	// ignore c.Resources.KernelMemory
-
-	return properties, nil
-}
-
-func (m *unifiedManager) Apply(pid int) error {
+func (m *UnifiedManager) Apply(pid int) error {
 	var (
-		c          = m.cgroups
+		c          = m.Cgroups
 		unitName   = getUnitName(c)
 		slice      = "system.slice"
 		properties []systemdDbus.Property
 	)
 
 	if c.Paths != nil {
-		return cgroups.WriteCgroupProc(m.path, pid)
+		paths := make(map[string]string)
+		for name, path := range c.Paths {
+			paths[name] = path
+		}
+		m.Paths = paths
+		return cgroups.EnterPid(m.Paths, pid)
 	}
 
 	if c.Parent != "" {
@@ -135,13 +79,55 @@ func (m *unifiedManager) Apply(pid int) error {
 	properties = append(properties,
 		newProp("DefaultDependencies", false))
 
-	resourcesProperties, err := genV2ResourcesProperties(c)
+	if c.Resources.Memory != 0 {
+		properties = append(properties,
+			newProp("MemoryMax", uint64(c.Resources.Memory)))
+	}
+
+	swap, err := cgroups.ConvertMemorySwapToCgroupV2Value(c.Resources.MemorySwap, c.Resources.Memory)
 	if err != nil {
 		return err
 	}
-	properties = append(properties, resourcesProperties...)
+	if swap > 0 {
+		properties = append(properties,
+			newProp("MemorySwapMax", uint64(swap)))
+	}
+
+	if c.Resources.CpuWeight != 0 {
+		properties = append(properties,
+			newProp("CPUWeight", c.Resources.CpuWeight))
+	}
+
+	// cpu.cfs_quota_us and cpu.cfs_period_us are controlled by systemd.
+	if c.Resources.CpuQuota != 0 && c.Resources.CpuPeriod != 0 {
+		// corresponds to USEC_INFINITY in systemd
+		// if USEC_INFINITY is provided, CPUQuota is left unbound by systemd
+		// always setting a property value ensures we can apply a quota and remove it later
+		cpuQuotaPerSecUSec := uint64(math.MaxUint64)
+		if c.Resources.CpuQuota > 0 {
+			// systemd converts CPUQuotaPerSecUSec (microseconds per CPU second) to CPUQuota
+			// (integer percentage of CPU) internally.  This means that if a fractional percent of
+			// CPU is indicated by Resources.CpuQuota, we need to round up to the nearest
+			// 10ms (1% of a second) such that child cgroups can set the cpu.cfs_quota_us they expect.
+			cpuQuotaPerSecUSec = uint64(c.Resources.CpuQuota*1000000) / c.Resources.CpuPeriod
+			if cpuQuotaPerSecUSec%10000 != 0 {
+				cpuQuotaPerSecUSec = ((cpuQuotaPerSecUSec / 10000) + 1) * 10000
+			}
+		}
+		properties = append(properties,
+			newProp("CPUQuotaPerSecUSec", cpuQuotaPerSecUSec))
+	}
+
+	if c.Resources.PidsLimit > 0 {
+		properties = append(properties,
+			newProp("TasksAccounting", true),
+			newProp("TasksMax", uint64(c.Resources.PidsLimit)))
+	}
+
 	properties = append(properties, c.SystemdProps...)
 
+	// ignore c.Resources.KernelMemory
+
 	dbusConnection, err := getDbusConnection()
 	if err != nil {
 		return err
@@ -158,18 +144,27 @@ func (m *unifiedManager) Apply(pid int) error {
 		return err
 	}
 
-	_, err = m.GetUnifiedPath()
+	path, err := getv2Path(m.Cgroups)
 	if err != nil {
 		return err
 	}
-	if err := fs2.CreateCgroupPath(m.path, m.cgroups); err != nil {
+	if err := createCgroupsv2Path(path); err != nil {
 		return err
 	}
+	m.Paths = map[string]string{
+		"pids":    path,
+		"memory":  path,
+		"io":      path,
+		"cpu":     path,
+		"devices": path,
+		"cpuset":  path,
+		"freezer": path,
+	}
 	return nil
 }
 
-func (m *unifiedManager) Destroy() error {
-	if m.cgroups.Paths != nil {
+func (m *UnifiedManager) Destroy() error {
+	if m.Cgroups.Paths != nil {
 		return nil
 	}
 	m.mu.Lock()
@@ -179,40 +174,40 @@ func (m *unifiedManager) Destroy() error {
 	if err != nil {
 		return err
 	}
-	dbusConnection.StopUnit(getUnitName(m.cgroups), "replace", nil)
-
-	// XXX this is probably not needed, systemd should handle it
-	err = os.Remove(m.path)
-	if err != nil && !os.IsNotExist(err) {
+	dbusConnection.StopUnit(getUnitName(m.Cgroups), "replace", nil)
+	if err := cgroups.RemovePaths(m.Paths); err != nil {
 		return err
 	}
-
+	m.Paths = make(map[string]string)
 	return nil
 }
 
-// this method is for v1 backward compatibility and will be removed
-func (m *unifiedManager) GetPaths() map[string]string {
-	_, _ = m.GetUnifiedPath()
-	paths := map[string]string{
-		"pids":    m.path,
-		"memory":  m.path,
-		"io":      m.path,
-		"cpu":     m.path,
-		"devices": m.path,
-		"cpuset":  m.path,
-		"freezer": m.path,
-	}
+func (m *UnifiedManager) GetPaths() map[string]string {
+	m.mu.Lock()
+	paths := m.Paths
+	m.mu.Unlock()
 	return paths
 }
-
-func (m *unifiedManager) GetUnifiedPath() (string, error) {
+func (m *UnifiedManager) GetUnifiedPath() (string, error) {
+	unifiedPath := ""
 	m.mu.Lock()
 	defer m.mu.Unlock()
-	if m.path != "" {
-		return m.path, nil
+	for k, v := range m.Paths {
+		if unifiedPath == "" {
+			unifiedPath = v
+		} else if v != unifiedPath {
+			return unifiedPath,
+				errors.Errorf("expected %q path to be unified path %q, got %q", k, unifiedPath, v)
+		}
+	}
+	if unifiedPath == "" {
+		// FIXME: unified path could be detected even when no controller is available
+		return unifiedPath, errors.New("cannot detect unified path")
 	}
+	return unifiedPath, nil
+}
 
-	c := m.cgroups
+func getv2Path(c *configs.Cgroup) (string, error) {
 	slice := "system.slice"
 	if c.Parent != "" {
 		slice = c.Parent
@@ -223,25 +218,54 @@ func (m *unifiedManager) GetUnifiedPath() (string, error) {
 		return "", err
 	}
 
-	path := filepath.Join(slice, getUnitName(c))
-	path, err = securejoin.SecureJoin(fs2.UnifiedMountpoint, path)
+	return filepath.Join(fs2.UnifiedMountpoint, slice, getUnitName(c)), nil
+}
+
+func createCgroupsv2Path(path string) (Err error) {
+	content, err := ioutil.ReadFile("/sys/fs/cgroup/cgroup.controllers")
 	if err != nil {
-		return "", err
+		return err
 	}
-	m.path = path
 
-	return m.path, nil
+	ctrs := bytes.Fields(content)
+	res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)
+
+	current := "/sys/fs"
+	elements := strings.Split(path, "/")
+	for i, e := range elements[3:] {
+		current = filepath.Join(current, e)
+		if i > 0 {
+			if err := os.Mkdir(current, 0755); err != nil {
+				if !os.IsExist(err) {
+					return err
+				}
+			} else {
+				// If the directory was created, be sure it is not left around on errors.
+				defer func() {
+					if Err != nil {
+						os.Remove(current)
+					}
+				}()
+			}
+		}
+		if i < len(elements[3:])-1 {
+			if err := ioutil.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0755); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
 }
 
-func (m *unifiedManager) fsManager() (cgroups.Manager, error) {
+func (m *UnifiedManager) fsManager() (cgroups.Manager, error) {
 	path, err := m.GetUnifiedPath()
 	if err != nil {
 		return nil, err
 	}
-	return fs2.NewManager(m.cgroups, path, m.rootless)
+	return fs2.NewManager(m.Cgroups, path, false)
 }
 
-func (m *unifiedManager) Freeze(state configs.FreezerState) error {
+func (m *UnifiedManager) Freeze(state configs.FreezerState) error {
 	fsMgr, err := m.fsManager()
 	if err != nil {
 		return err
@@ -249,7 +273,7 @@ func (m *unifiedManager) Freeze(state configs.FreezerState) error {
 	return fsMgr.Freeze(state)
 }
 
-func (m *unifiedManager) GetPids() ([]int, error) {
+func (m *UnifiedManager) GetPids() ([]int, error) {
 	path, err := m.GetUnifiedPath()
 	if err != nil {
 		return nil, err
@@ -257,7 +281,7 @@ func (m *unifiedManager) GetPids() ([]int, error) {
 	return cgroups.GetPids(path)
 }
 
-func (m *unifiedManager) GetAllPids() ([]int, error) {
+func (m *UnifiedManager) GetAllPids() ([]int, error) {
 	path, err := m.GetUnifiedPath()
 	if err != nil {
 		return nil, err
@@ -265,7 +289,7 @@ func (m *unifiedManager) GetAllPids() ([]int, error) {
 	return cgroups.GetAllPids(path)
 }
 
-func (m *unifiedManager) GetStats() (*cgroups.Stats, error) {
+func (m *UnifiedManager) GetStats() (*cgroups.Stats, error) {
 	fsMgr, err := m.fsManager()
 	if err != nil {
 		return nil, err
@@ -273,19 +297,7 @@ func (m *unifiedManager) GetStats() (*cgroups.Stats, error) {
 	return fsMgr.GetStats()
 }
 
-func (m *unifiedManager) Set(container *configs.Config) error {
-	properties, err := genV2ResourcesProperties(m.cgroups)
-	if err != nil {
-		return err
-	}
-	dbusConnection, err := getDbusConnection()
-	if err != nil {
-		return err
-	}
-	if err := dbusConnection.SetUnitProperties(getUnitName(m.cgroups), true, properties...); err != nil {
-		return err
-	}
-
+func (m *UnifiedManager) Set(container *configs.Config) error {
 	fsMgr, err := m.fsManager()
 	if err != nil {
 		return err
@@ -293,6 +305,6 @@ func (m *unifiedManager) Set(container *configs.Config) error {
 	return fsMgr.Set(container)
 }
 
-func (m *unifiedManager) GetCgroups() (*configs.Cgroup, error) {
-	return m.cgroups, nil
+func (m *UnifiedManager) GetCgroups() (*configs.Cgroup, error) {
+	return m.Cgroups, nil
 }
diff --git a/libcontainer/cgroups/utils.go b/libcontainer/cgroups/utils.go
index f1e11160..8002c052 100644
--- a/libcontainer/cgroups/utils.go
+++ b/libcontainer/cgroups/utils.go
@@ -13,6 +13,7 @@ import (
 	"strconv"
 	"strings"
 	"sync"
+	"syscall"
 	"time"
 
 	units "github.com/docker/go-units"
@@ -42,8 +43,8 @@ var HugePageSizeUnitList = []string{"B", "KB", "MB", "GB", "TB", "PB"}
 // IsCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode.
 func IsCgroup2UnifiedMode() bool {
 	isUnifiedOnce.Do(func() {
-		var st unix.Statfs_t
-		if err := unix.Statfs(unifiedMountpoint, &st); err != nil {
+		var st syscall.Statfs_t
+		if err := syscall.Statfs(unifiedMountpoint, &st); err != nil {
 			panic("cannot statfs cgroup root")
 		}
 		isUnified = st.Type == unix.CGROUP2_SUPER_MAGIC
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index 96bc66b2..40797434 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -16,6 +16,7 @@ import (
 	"reflect"
 	"strings"
 	"sync"
+	"syscall" // only for SysProcAttr and Signal
 	"time"
 
 	securejoin "github.com/cyphar/filepath-securejoin"
@@ -308,7 +309,7 @@ func awaitFifoOpen(path string) <-chan openResult {
 func fifoOpen(path string, block bool) openResult {
 	flags := os.O_RDONLY
 	if !block {
-		flags |= unix.O_NONBLOCK
+		flags |= syscall.O_NONBLOCK
 	}
 	f, err := os.OpenFile(path, flags, 0)
 	if err != nil {
@@ -479,7 +480,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childInitPipe *os.File, chi
 	cmd.Stderr = p.Stderr
 	cmd.Dir = c.config.Rootfs
 	if cmd.SysProcAttr == nil {
-		cmd.SysProcAttr = &unix.SysProcAttr{}
+		cmd.SysProcAttr = &syscall.SysProcAttr{}
 	}
 	cmd.Env = append(cmd.Env, fmt.Sprintf("GOMAXPROCS=%s", os.Getenv("GOMAXPROCS")))
 	cmd.ExtraFiles = append(cmd.ExtraFiles, p.ExtraFiles...)
@@ -505,7 +506,7 @@ func (c *linuxContainer) commandTemplate(p *Process, childInitPipe *os.File, chi
 	// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
 	// even with the parent still running.
 	if c.config.ParentDeathSignal > 0 {
-		cmd.SysProcAttr.Pdeathsig = unix.Signal(c.config.ParentDeathSignal)
+		cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal)
 	}
 	return cmd
 }
@@ -1004,8 +1005,8 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
 			// CRIU expects the information about an external namespace
 			// like this: --external net[<inode>]:<key>
 			// This <key> is always 'extRootNetNS'.
-			var netns unix.Stat_t
-			err = unix.Stat(nsPath, &netns)
+			var netns syscall.Stat_t
+			err = syscall.Stat(nsPath, &netns)
 			if err != nil {
 				return err
 			}
@@ -1014,25 +1015,15 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
 		}
 	}
 
-	// CRIU can use cgroup freezer; when rpcOpts.FreezeCgroup
-	// is not set, CRIU uses ptrace() to pause the processes.
-	var fcg string
-	switch cgroups.IsCgroup2UnifiedMode() {
-	case false:
-		fcg = c.cgroupManager.GetPaths()["freezer"]
-	case true:
-		// cgroup v2 freezer is only supported since CRIU release 3.14
-		if c.checkCriuVersion(31400) == nil {
-			fcg, err = c.cgroupManager.GetUnifiedPath()
-			if err != nil {
-				// should not happen
-				return err
-			}
+	if !cgroups.IsCgroup2UnifiedMode() && c.checkCriuVersion(31400) == nil {
+		// CRIU currently cannot handle the v2 freezer correctly
+		// before release 3.14. For older releases we are telling
+		// CRIU to not use the cgroup v2 freezer. CRIU will pause
+		// each process manually using ptrace().
+		if fcg := c.cgroupManager.GetPaths()["freezer"]; fcg != "" {
+			rpcOpts.FreezeCgroup = proto.String(fcg)
 		}
 	}
-	if fcg != "" {
-		rpcOpts.FreezeCgroup = proto.String(fcg)
-	}
 
 	// append optional criu opts, e.g., page-server and port
 	if criuOpts.PageServer.Address != "" && criuOpts.PageServer.Port != 0 {
@@ -1095,19 +1086,18 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
 		go waitForCriuLazyServer(statusRead, criuOpts.StatusFd)
 	}
 
-	// no need to dump all this in pre-dump
+	//no need to dump these information in pre-dump
 	if !criuOpts.PreDump {
-		hasCgroupns := c.config.Namespaces.Contains(configs.NEWCGROUP)
 		for _, m := range c.config.Mounts {
 			switch m.Device {
 			case "bind":
 				c.addCriuDumpMount(req, m)
 			case "cgroup":
-				if cgroups.IsCgroup2UnifiedMode() || hasCgroupns {
-					// real mount(s)
+				if cgroups.IsCgroup2UnifiedMode() {
+					c.addCriuDumpMount(req, m)
 					continue
 				}
-				// a set of "external" bind mounts
+				// cgroup v1
 				binds, err := getCgroupMounts(m)
 				if err != nil {
 					return err
@@ -1185,14 +1175,7 @@ func (c *linuxContainer) restoreNetwork(req *criurpc.CriuReq, criuOpts *CriuOpts
 func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
 	switch m.Device {
 	case "cgroup":
-		// No mount point(s) need to be created:
-		//
-		// * for v1, mount points are saved by CRIU because
-		//   /sys/fs/cgroup is a tmpfs mount
-		//
-		// * for v2, /sys/fs/cgroup is a real mount, but
-		//   the mountpoint appears as soon as /sys is mounted
-		return nil
+		// Do nothing for cgroup, CRIU should handle it
 	case "bind":
 		// The prepareBindMount() function checks if source
 		// exists. So it cannot be used for other filesystem types.
@@ -1200,7 +1183,7 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
 			return err
 		}
 	default:
-		// for all other filesystems just create the mountpoints
+		// for all other file-systems just create the mountpoints
 		dest, err := securejoin.SecureJoin(c.config.Rootfs, m.Destination)
 		if err != nil {
 			return err
@@ -1352,11 +1335,11 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
 			// The <key> needs to be the same as during checkpointing.
 			// We are always using 'extRootNetNS' as the key in this.
 			netns, err := os.Open(nsPath)
+			defer netns.Close()
 			if err != nil {
 				logrus.Errorf("If a specific network namespace is defined it must exist: %s", err)
 				return fmt.Errorf("Requested network namespace %v does not exist", nsPath)
 			}
-			defer netns.Close()
 			inheritFd := new(criurpc.InheritFd)
 			inheritFd.Key = proto.String("extRootNetNS")
 			// The offset of four is necessary because 0, 1, 2 and 3 is already
@@ -1374,16 +1357,16 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
 		return err
 	}
 
-	hasCgroupns := c.config.Namespaces.Contains(configs.NEWCGROUP)
 	for _, m := range c.config.Mounts {
 		switch m.Device {
 		case "bind":
 			c.addCriuRestoreMount(req, m)
 		case "cgroup":
-			if cgroups.IsCgroup2UnifiedMode() || hasCgroupns {
+			if cgroups.IsCgroup2UnifiedMode() {
+				c.addCriuRestoreMount(req, m)
 				continue
 			}
-			// cgroup v1 is a set of bind mounts, unless cgroupns is used
+			// cgroup v1
 			binds, err := getCgroupMounts(m)
 			if err != nil {
 				return err
@@ -1874,7 +1857,7 @@ func (c *linuxContainer) isPaused() (bool, error) {
 	data, err := ioutil.ReadFile(filepath.Join(fcg, filename))
 	if err != nil {
 		// If freezer cgroup is not mounted, the container would just be not paused.
-		if os.IsNotExist(err) || errors.Is(err, unix.ENODEV) {
+		if os.IsNotExist(err) || errors.Is(err, syscall.ENODEV) {
 			return false, nil
 		}
 		return false, newSystemErrorWithCause(err, "checking if container is paused")
diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go
index 03c6ec45..a572c0f6 100644
--- a/libcontainer/factory_linux.go
+++ b/libcontainer/factory_linux.go
@@ -50,51 +50,28 @@ func InitArgs(args ...string) func(*LinuxFactory) error {
 	}
 }
 
-func getUnifiedPath(paths map[string]string) string {
-	path := ""
-	for k, v := range paths {
-		if path == "" {
-			path = v
-		} else if v != path {
-			panic(errors.Errorf("expected %q path to be unified path %q, got %q", k, path, v))
-		}
-	}
-	// can be empty
-	if path != "" {
-		if filepath.Clean(path) != path || !filepath.IsAbs(path) {
-			panic(errors.Errorf("invalid dir path %q", path))
-		}
-	}
-
-	return path
-}
-
-func systemdCgroupV2(l *LinuxFactory, rootless bool) error {
-	l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
-		return systemd.NewUnifiedManager(config, getUnifiedPath(paths), rootless)
-	}
-	return nil
-}
-
 // SystemdCgroups is an options func to configure a LinuxFactory to return
 // containers that use systemd to create and manage cgroups.
 func SystemdCgroups(l *LinuxFactory) error {
-	if !systemd.IsRunningSystemd() {
-		return fmt.Errorf("systemd not running on this host, can't use systemd as cgroups manager")
-	}
-
-	if cgroups.IsCgroup2UnifiedMode() {
-		return systemdCgroupV2(l, false)
+	systemdCgroupsManager, err := systemd.NewSystemdCgroupsManager()
+	if err != nil {
+		return err
 	}
+	l.NewCgroupsManager = systemdCgroupsManager
+	return nil
+}
 
-	l.NewCgroupsManager = func(config *configs.Cgroup, paths map[string]string) cgroups.Manager {
-		return &systemd.LegacyManager{
-			Cgroups: config,
-			Paths:   paths,
+func getUnifiedPath(paths map[string]string) string {
+	unifiedPath := ""
+	for k, v := range paths {
+		if unifiedPath == "" {
+			unifiedPath = v
+		} else if v != unifiedPath {
+			panic(errors.Errorf("expected %q path to be unified path %q, got %q", k, unifiedPath, v))
 		}
 	}
-
-	return nil
+	// can be empty
+	return unifiedPath
 }
 
 func cgroupfs2(l *LinuxFactory, rootless bool) error {
diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go
index f7b4b892..c1b15600 100644
--- a/libcontainer/init_linux.go
+++ b/libcontainer/init_linux.go
@@ -10,6 +10,7 @@ import (
 	"net"
 	"os"
 	"strings"
+	"syscall" // only for Errno
 	"unsafe"
 
 	"golang.org/x/sys/unix"
@@ -454,7 +455,7 @@ func isWaitable(pid int) (bool, error) {
 // isNoChildren returns true if err represents a unix.ECHILD (formerly syscall.ECHILD) false otherwise
 func isNoChildren(err error) bool {
 	switch err := err.(type) {
-	case unix.Errno:
+	case syscall.Errno:
 		if err == unix.ECHILD {
 			return true
 		}
diff --git a/libcontainer/intelrdt/cmt.go b/libcontainer/intelrdt/cmt.go
new file mode 100644
index 00000000..5c406e10
--- /dev/null
+++ b/libcontainer/intelrdt/cmt.go
@@ -0,0 +1,22 @@
+package intelrdt
+
+var (
+	cmtEnabled bool
+)
+
+// Check if Intel RDT/CMT is enabled.
+func IsCMTEnabled() bool {
+	return cmtEnabled
+}
+
+func getCMTNumaNodeStats(numaPath string) (*CMTNumaNodeStats, error) {
+	stats := &CMTNumaNodeStats{}
+
+	llcOccupancy, err := getIntelRdtParamUint(numaPath, "llc_occupancy")
+	if err != nil {
+		return nil, err
+	}
+	stats.LLCOccupancy = llcOccupancy
+
+	return stats, nil
+}
diff --git a/libcontainer/intelrdt/intelrdt.go b/libcontainer/intelrdt/intelrdt.go
index b19580f1..5b19d55a 100644
--- a/libcontainer/intelrdt/intelrdt.go
+++ b/libcontainer/intelrdt/intelrdt.go
@@ -55,6 +55,10 @@ import (
  * |   |   |-- cbm_mask
  * |   |   |-- min_cbm_bits
  * |   |   |-- num_closids
+ * |   |-- L3_MON
+ * |   |   |-- max_threshold_occupancy
+ * |   |   |-- mon_features
+ * |   |   |-- num_rmids
  * |   |-- MB
  * |       |-- bandwidth_gran
  * |       |-- delay_linear
@@ -191,8 +195,7 @@ type intelRdtData struct {
 // Check if Intel RDT sub-features are enabled in init()
 func init() {
 	// 1. Check if hardware and kernel support Intel RDT sub-features
-	// "cat_l3" flag for CAT and "mba" flag for MBA
-	isCatFlagSet, isMbaFlagSet, err := parseCpuInfoFile("/proc/cpuinfo")
+	flagsSet, err := parseCpuInfoFile("/proc/cpuinfo")
 	if err != nil {
 		return
 	}
@@ -207,7 +210,7 @@ func init() {
 	// "resource control" filesystem. Intel RDT sub-features can be
 	// selectively disabled or enabled by kernel command line
 	// (e.g., rdt=!l3cat,mba) in 4.14 and newer kernel
-	if isCatFlagSet {
+	if flagsSet.CAT {
 		if _, err := os.Stat(filepath.Join(intelRdtRoot, "info", "L3")); err == nil {
 			isCatEnabled = true
 		}
@@ -217,11 +220,23 @@ func init() {
 		// MBA should be enabled because MBA Software Controller
 		// depends on MBA
 		isMbaEnabled = true
-	} else if isMbaFlagSet {
+	} else if flagsSet.MBA {
 		if _, err := os.Stat(filepath.Join(intelRdtRoot, "info", "MB")); err == nil {
 			isMbaEnabled = true
 		}
 	}
+
+	if flagsSet.MBMTotal || flagsSet.MBMLocal {
+		if _, err := os.Stat(filepath.Join(intelRdtRoot, "info", "L3_MON")); err == nil {
+			mbmEnabled = true
+			cmtEnabled = true
+		}
+
+		enabledMonFeatures, err = getMonFeatures(intelRdtRoot)
+		if err != nil {
+			return
+		}
+	}
 }
 
 // Return the mount point path of Intel RDT "resource control" filesysem
@@ -298,13 +313,21 @@ func isIntelRdtMounted() bool {
 	return true
 }
 
-func parseCpuInfoFile(path string) (bool, bool, error) {
-	isCatFlagSet := false
-	isMbaFlagSet := false
+type cpuInfoFlags struct {
+	CAT bool // Cache Allocation Technology
+	MBA bool // Memory Bandwidth Allocation
+
+	// Memory Bandwidth Monitoring related.
+	MBMTotal bool
+	MBMLocal bool
+}
+
+func parseCpuInfoFile(path string) (cpuInfoFlags, error) {
+	infoFlags := cpuInfoFlags{}
 
 	f, err := os.Open(path)
 	if err != nil {
-		return false, false, err
+		return infoFlags, err
 	}
 	defer f.Close()
 
@@ -319,19 +342,23 @@ func parseCpuInfoFile(path string) (bool, bool, error) {
 			for _, flag := range flags {
 				switch flag {
 				case "cat_l3":
-					isCatFlagSet = true
+					infoFlags.CAT = true
 				case "mba":
-					isMbaFlagSet = true
+					infoFlags.MBA = true
+				case "cqm_mbm_total":
+					infoFlags.MBMTotal = true
+				case "cqm_mbm_local":
+					infoFlags.MBMLocal = true
 				}
 			}
-			return isCatFlagSet, isMbaFlagSet, nil
+			return infoFlags, nil
 		}
 	}
 	if err := s.Err(); err != nil {
-		return false, false, err
+		return infoFlags, err
 	}
 
-	return isCatFlagSet, isMbaFlagSet, nil
+	return infoFlags, nil
 }
 
 func parseUint(s string, base, bitSize int) (uint64, error) {
@@ -586,7 +613,8 @@ func (m *IntelRdtManager) GetStats() (*Stats, error) {
 	schemaRootStrings := strings.Split(tmpRootStrings, "\n")
 
 	// The L3 cache and memory bandwidth schemata in 'container_id' group
-	tmpStrings, err := getIntelRdtParamString(m.GetPath(), "schemata")
+	containerPath := m.GetPath()
+	tmpStrings, err := getIntelRdtParamString(containerPath, "schemata")
 	if err != nil {
 		return nil, err
 	}
@@ -638,6 +666,11 @@ func (m *IntelRdtManager) GetStats() (*Stats, error) {
 		}
 	}
 
+	err = getMonitoringStats(containerPath, stats)
+	if err != nil {
+		return nil, err
+	}
+
 	return stats, nil
 }
 
diff --git a/libcontainer/intelrdt/mbm.go b/libcontainer/intelrdt/mbm.go
new file mode 100644
index 00000000..3730bab5
--- /dev/null
+++ b/libcontainer/intelrdt/mbm.go
@@ -0,0 +1,34 @@
+// +build linux
+
+package intelrdt
+
+var (
+	// The flag to indicate if Intel RDT/MBM is enabled
+	mbmEnabled bool
+)
+
+// Check if Intel RDT/MBM is enabled.
+func IsMBMEnabled() bool {
+	return mbmEnabled
+}
+
+func getMBMNumaNodeStats(numaPath string) (*MBMNumaNodeStats, error) {
+	stats := &MBMNumaNodeStats{}
+	if enabledMonFeatures.mbmTotalBytes {
+		mbmTotalBytes, err := getIntelRdtParamUint(numaPath, "mbm_total_bytes")
+		if err != nil {
+			return nil, err
+		}
+		stats.MBMTotalBytes = mbmTotalBytes
+	}
+
+	if enabledMonFeatures.mbmLocalBytes {
+		mbmLocalBytes, err := getIntelRdtParamUint(numaPath, "mbm_local_bytes")
+		if err != nil {
+			return nil, err
+		}
+		stats.MBMLocalBytes = mbmLocalBytes
+	}
+
+	return stats, nil
+}
diff --git a/libcontainer/intelrdt/monitoring.go b/libcontainer/intelrdt/monitoring.go
new file mode 100644
index 00000000..4ccc8eae
--- /dev/null
+++ b/libcontainer/intelrdt/monitoring.go
@@ -0,0 +1,85 @@
+package intelrdt
+
+import (
+	"bufio"
+	"github.com/sirupsen/logrus"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+)
+
+var (
+	enabledMonFeatures monFeatures
+)
+
+type monFeatures struct {
+	mbmTotalBytes bool
+	mbmLocalBytes bool
+	llcOccupancy  bool
+}
+
+func getMonFeatures(intelRdtRoot string) (monFeatures, error) {
+	file, err := os.Open(filepath.Join(intelRdtRoot, "info", "L3_MON", "mon_features"))
+	defer file.Close()
+	if err != nil {
+		return monFeatures{}, err
+	}
+	return parseMonFeatures(file)
+}
+
+func parseMonFeatures(reader io.Reader) (monFeatures, error) {
+	scanner := bufio.NewScanner(reader)
+
+	monFeatures := monFeatures{}
+
+	for scanner.Scan() {
+		switch feature := scanner.Text(); feature {
+		case "mbm_total_bytes":
+			monFeatures.mbmTotalBytes = true
+		case "mbm_local_bytes":
+			monFeatures.mbmLocalBytes = true
+		case "llc_occupancy":
+			monFeatures.llcOccupancy = true
+		default:
+			logrus.Warnf("Unsupported Intel RDT monitoring feature: %s", feature)
+		}
+	}
+
+	return monFeatures, scanner.Err()
+}
+
+func getMonitoringStats(containerPath string, stats *Stats) error {
+	numaFiles, err := ioutil.ReadDir(filepath.Join(containerPath, "mon_data"))
+	if err != nil {
+		return err
+	}
+
+	var mbmStats []MBMNumaNodeStats
+	var cmtStats []CMTNumaNodeStats
+
+	for _, file := range numaFiles {
+		if file.IsDir() {
+			numaPath := filepath.Join(containerPath, "mon_data", file.Name())
+			if IsMBMEnabled() {
+				numaMBMStats, err := getMBMNumaNodeStats(numaPath)
+				if err != nil {
+					return err
+				}
+				mbmStats = append(mbmStats, *numaMBMStats)
+			}
+			if IsCMTEnabled() {
+				numaCMTStats, err := getCMTNumaNodeStats(numaPath)
+				if err != nil {
+					return err
+				}
+				cmtStats = append(cmtStats, *numaCMTStats)
+			}
+		}
+	}
+
+	stats.MBMStats = &mbmStats
+	stats.CMTStats = &cmtStats
+
+	return err
+}
diff --git a/libcontainer/intelrdt/stats.go b/libcontainer/intelrdt/stats.go
index df5686f3..eeb0ee9f 100644
--- a/libcontainer/intelrdt/stats.go
+++ b/libcontainer/intelrdt/stats.go
@@ -15,6 +15,19 @@ type MemBwInfo struct {
 	NumClosids    uint64 `json:"num_closids,omitempty"`
 }
 
+type MBMNumaNodeStats struct {
+	// The 'mbm_total_bytes' in 'container_id' group.
+	MBMTotalBytes uint64 `json:"mbm_total_bytes,omitempty"`
+
+	// The 'mbm_local_bytes' in 'container_id' group.
+	MBMLocalBytes uint64 `json:"mbm_local_bytes,omitempty"`
+}
+
+type CMTNumaNodeStats struct {
+	// The 'llc_occupancy' in 'container_id' group.
+	LLCOccupancy uint64 `json:"llc_occupancy,omitempty"`
+}
+
 type Stats struct {
 	// The read-only L3 cache information
 	L3CacheInfo *L3CacheInfo `json:"l3_cache_info,omitempty"`
@@ -33,6 +46,12 @@ type Stats struct {
 
 	// The memory bandwidth schema in 'container_id' group
 	MemBwSchema string `json:"mem_bw_schema,omitempty"`
+
+	// The memory bandwidth monitoring statistics from NUMA nodes in 'container_id' group
+	MBMStats *[]MBMNumaNodeStats `json:"mbm_stats,omitempty"`
+
+	// The cache monitoring technology statistics from NUMA nodes in 'container_id' group
+	CMTStats *[]CMTNumaNodeStats `json:"cmt_stats,omitempty"`
 }
 
 func NewStats() *Stats {
diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go
index a18cfa57..51e54aca 100644
--- a/libcontainer/process_linux.go
+++ b/libcontainer/process_linux.go
@@ -11,6 +11,7 @@ import (
 	"os/exec"
 	"path/filepath"
 	"strconv"
+	"syscall" // only for Signal
 
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/configs"
@@ -75,7 +76,7 @@ func (p *setnsProcess) startTime() (uint64, error) {
 }
 
 func (p *setnsProcess) signal(sig os.Signal) error {
-	s, ok := sig.(unix.Signal)
+	s, ok := sig.(syscall.Signal)
 	if !ok {
 		return errors.New("os: unsupported signal type")
 	}
@@ -505,7 +506,7 @@ func (p *initProcess) createNetworkInterfaces() error {
 }
 
 func (p *initProcess) signal(sig os.Signal) error {
-	s, ok := sig.(unix.Signal)
+	s, ok := sig.(syscall.Signal)
 	if !ok {
 		return errors.New("os: unsupported signal type")
 	}
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
index df2fa03d..84c5143a 100644
--- a/libcontainer/rootfs_linux.go
+++ b/libcontainer/rootfs_linux.go
@@ -244,7 +244,7 @@ func mountCgroupV1(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
 			}
 			cgroupmount := &configs.Mount{
 				Source:      "cgroup",
-				Device:      "cgroup", // this is actually fstype
+				Device:      "cgroup",
 				Destination: subsystemPath,
 				Flags:       flags,
 				Data:        filepath.Base(subsystemPath),
@@ -402,9 +402,27 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
 		}
 	case "cgroup":
 		if cgroups.IsCgroup2UnifiedMode() {
-			return mountCgroupV2(m, rootfs, mountLabel, enableCgroupns)
+			if err := mountCgroupV2(m, rootfs, mountLabel, enableCgroupns); err != nil {
+				return err
+			}
+		} else {
+
+			if err := mountCgroupV1(m, rootfs, mountLabel, enableCgroupns); err != nil {
+				return err
+			}
+		}
+		if m.Flags&unix.MS_RDONLY != 0 {
+			// remount cgroup root as readonly
+			mcgrouproot := &configs.Mount{
+				Source:      m.Destination,
+				Device:      "bind",
+				Destination: m.Destination,
+				Flags:       defaultMountFlags | unix.MS_RDONLY | unix.MS_BIND,
+			}
+			if err := remount(mcgrouproot, rootfs); err != nil {
+				return err
+			}
 		}
-		return mountCgroupV1(m, rootfs, mountLabel, enableCgroupns)
 	default:
 		// ensure that the destination of the mount is resolved of symlinks at mount time because
 		// any previous mounts can invalidate the next mount's destination.
diff --git a/libcontainer/specconv/example.go b/libcontainer/specconv/example.go
index 8a201bc7..23e225c3 100644
--- a/libcontainer/specconv/example.go
+++ b/libcontainer/specconv/example.go
@@ -4,14 +4,13 @@ import (
 	"os"
 	"strings"
 
-	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runtime-spec/specs-go"
 )
 
 // Example returns an example spec file, with many options set so a user can
 // see what a standard spec file looks like.
 func Example() *specs.Spec {
-	spec := &specs.Spec{
+	return &specs.Spec{
 		Version: specs.Version,
 		Root: &specs.Root{
 			Path:     "rootfs",
@@ -139,29 +138,23 @@ func Example() *specs.Spec {
 			},
 			Namespaces: []specs.LinuxNamespace{
 				{
-					Type: specs.PIDNamespace,
+					Type: "pid",
 				},
 				{
-					Type: specs.NetworkNamespace,
+					Type: "network",
 				},
 				{
-					Type: specs.IPCNamespace,
+					Type: "ipc",
 				},
 				{
-					Type: specs.UTSNamespace,
+					Type: "uts",
 				},
 				{
-					Type: specs.MountNamespace,
+					Type: "mount",
 				},
 			},
 		},
 	}
-	if cgroups.IsCgroup2UnifiedMode() {
-		spec.Linux.Namespaces = append(spec.Linux.Namespaces, specs.LinuxNamespace{
-			Type: specs.CgroupNamespace,
-		})
-	}
-	return spec
 }
 
 // ToRootless converts the given spec file into one that should work with
diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go
index 727fdbd1..f372a138 100644
--- a/libcontainer/specconv/spec_linux.go
+++ b/libcontainer/specconv/spec_linux.go
@@ -422,160 +422,161 @@ func CreateCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
 	c.Resources.AllowedDevices = AllowedDevices
 	if spec.Linux != nil {
 		r := spec.Linux.Resources
-		if r != nil {
-			for i, d := range spec.Linux.Resources.Devices {
-				var (
-					t     = "a"
-					major = int64(-1)
-					minor = int64(-1)
-				)
-				if d.Type != "" {
-					t = d.Type
-				}
-				if d.Major != nil {
-					major = *d.Major
-				}
-				if d.Minor != nil {
-					minor = *d.Minor
-				}
-				if d.Access == "" {
-					return nil, fmt.Errorf("device access at %d field cannot be empty", i)
-				}
-				dt, err := stringToCgroupDeviceRune(t)
-				if err != nil {
-					return nil, err
-				}
-				dd := &configs.Device{
-					Type:        dt,
-					Major:       major,
-					Minor:       minor,
-					Permissions: d.Access,
-					Allow:       d.Allow,
-				}
-				c.Resources.Devices = append(c.Resources.Devices, dd)
+		if r == nil {
+			return c, nil
+		}
+		for i, d := range spec.Linux.Resources.Devices {
+			var (
+				t     = "a"
+				major = int64(-1)
+				minor = int64(-1)
+			)
+			if d.Type != "" {
+				t = d.Type
 			}
-			if r.Memory != nil {
-				if r.Memory.Limit != nil {
-					c.Resources.Memory = *r.Memory.Limit
-				}
-				if r.Memory.Reservation != nil {
-					c.Resources.MemoryReservation = *r.Memory.Reservation
-				}
-				if r.Memory.Swap != nil {
-					c.Resources.MemorySwap = *r.Memory.Swap
-				}
-				if r.Memory.Kernel != nil {
-					c.Resources.KernelMemory = *r.Memory.Kernel
-				}
-				if r.Memory.KernelTCP != nil {
-					c.Resources.KernelMemoryTCP = *r.Memory.KernelTCP
-				}
-				if r.Memory.Swappiness != nil {
-					c.Resources.MemorySwappiness = r.Memory.Swappiness
-				}
-				if r.Memory.DisableOOMKiller != nil {
-					c.Resources.OomKillDisable = *r.Memory.DisableOOMKiller
-				}
+			if d.Major != nil {
+				major = *d.Major
 			}
-			if r.CPU != nil {
-				if r.CPU.Shares != nil {
-					c.Resources.CpuShares = *r.CPU.Shares
+			if d.Minor != nil {
+				minor = *d.Minor
+			}
+			if d.Access == "" {
+				return nil, fmt.Errorf("device access at %d field cannot be empty", i)
+			}
+			dt, err := stringToCgroupDeviceRune(t)
+			if err != nil {
+				return nil, err
+			}
+			dd := &configs.Device{
+				Type:        dt,
+				Major:       major,
+				Minor:       minor,
+				Permissions: d.Access,
+				Allow:       d.Allow,
+			}
+			c.Resources.Devices = append(c.Resources.Devices, dd)
+		}
+		if r.Memory != nil {
+			if r.Memory.Limit != nil {
+				c.Resources.Memory = *r.Memory.Limit
+			}
+			if r.Memory.Reservation != nil {
+				c.Resources.MemoryReservation = *r.Memory.Reservation
+			}
+			if r.Memory.Swap != nil {
+				c.Resources.MemorySwap = *r.Memory.Swap
+			}
+			if r.Memory.Kernel != nil {
+				c.Resources.KernelMemory = *r.Memory.Kernel
+			}
+			if r.Memory.KernelTCP != nil {
+				c.Resources.KernelMemoryTCP = *r.Memory.KernelTCP
+			}
+			if r.Memory.Swappiness != nil {
+				c.Resources.MemorySwappiness = r.Memory.Swappiness
+			}
+			if r.Memory.DisableOOMKiller != nil {
+				c.Resources.OomKillDisable = *r.Memory.DisableOOMKiller
+			}
+		}
+		if r.CPU != nil {
+			if r.CPU.Shares != nil {
+				c.Resources.CpuShares = *r.CPU.Shares
 
-					//CpuWeight is used for cgroupv2 and should be converted
-					c.Resources.CpuWeight = cgroups.ConvertCPUSharesToCgroupV2Value(c.Resources.CpuShares)
-				}
-				if r.CPU.Quota != nil {
-					c.Resources.CpuQuota = *r.CPU.Quota
-				}
-				if r.CPU.Period != nil {
-					c.Resources.CpuPeriod = *r.CPU.Period
-				}
-				//CpuMax is used for cgroupv2 and should be converted
-				c.Resources.CpuMax = cgroups.ConvertCPUQuotaCPUPeriodToCgroupV2Value(c.Resources.CpuQuota, c.Resources.CpuPeriod)
+				//CpuWeight is used for cgroupv2 and should be converted
+				c.Resources.CpuWeight = cgroups.ConvertCPUSharesToCgroupV2Value(c.Resources.CpuShares)
+			}
+			if r.CPU.Quota != nil {
+				c.Resources.CpuQuota = *r.CPU.Quota
+			}
+			if r.CPU.Period != nil {
+				c.Resources.CpuPeriod = *r.CPU.Period
+			}
+			//CpuMax is used for cgroupv2 and should be converted
+			c.Resources.CpuMax = cgroups.ConvertCPUQuotaCPUPeriodToCgroupV2Value(c.Resources.CpuQuota, c.Resources.CpuPeriod)
 
-				if r.CPU.RealtimeRuntime != nil {
-					c.Resources.CpuRtRuntime = *r.CPU.RealtimeRuntime
-				}
-				if r.CPU.RealtimePeriod != nil {
-					c.Resources.CpuRtPeriod = *r.CPU.RealtimePeriod
-				}
-				if r.CPU.Cpus != "" {
-					c.Resources.CpusetCpus = r.CPU.Cpus
-				}
-				if r.CPU.Mems != "" {
-					c.Resources.CpusetMems = r.CPU.Mems
-				}
+			if r.CPU.RealtimeRuntime != nil {
+				c.Resources.CpuRtRuntime = *r.CPU.RealtimeRuntime
 			}
-			if r.Pids != nil {
-				c.Resources.PidsLimit = r.Pids.Limit
+			if r.CPU.RealtimePeriod != nil {
+				c.Resources.CpuRtPeriod = *r.CPU.RealtimePeriod
 			}
-			if r.BlockIO != nil {
-				if r.BlockIO.Weight != nil {
-					c.Resources.BlkioWeight = *r.BlockIO.Weight
-				}
-				if r.BlockIO.LeafWeight != nil {
-					c.Resources.BlkioLeafWeight = *r.BlockIO.LeafWeight
-				}
-				if r.BlockIO.WeightDevice != nil {
-					for _, wd := range r.BlockIO.WeightDevice {
-						var weight, leafWeight uint16
-						if wd.Weight != nil {
-							weight = *wd.Weight
-						}
-						if wd.LeafWeight != nil {
-							leafWeight = *wd.LeafWeight
-						}
-						weightDevice := configs.NewWeightDevice(wd.Major, wd.Minor, weight, leafWeight)
-						c.Resources.BlkioWeightDevice = append(c.Resources.BlkioWeightDevice, weightDevice)
-					}
-				}
-				if r.BlockIO.ThrottleReadBpsDevice != nil {
-					for _, td := range r.BlockIO.ThrottleReadBpsDevice {
-						rate := td.Rate
-						throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
-						c.Resources.BlkioThrottleReadBpsDevice = append(c.Resources.BlkioThrottleReadBpsDevice, throttleDevice)
-					}
-				}
-				if r.BlockIO.ThrottleWriteBpsDevice != nil {
-					for _, td := range r.BlockIO.ThrottleWriteBpsDevice {
-						rate := td.Rate
-						throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
-						c.Resources.BlkioThrottleWriteBpsDevice = append(c.Resources.BlkioThrottleWriteBpsDevice, throttleDevice)
+			if r.CPU.Cpus != "" {
+				c.Resources.CpusetCpus = r.CPU.Cpus
+			}
+			if r.CPU.Mems != "" {
+				c.Resources.CpusetMems = r.CPU.Mems
+			}
+		}
+		if r.Pids != nil {
+			c.Resources.PidsLimit = r.Pids.Limit
+		}
+		if r.BlockIO != nil {
+			if r.BlockIO.Weight != nil {
+				c.Resources.BlkioWeight = *r.BlockIO.Weight
+			}
+			if r.BlockIO.LeafWeight != nil {
+				c.Resources.BlkioLeafWeight = *r.BlockIO.LeafWeight
+			}
+			if r.BlockIO.WeightDevice != nil {
+				for _, wd := range r.BlockIO.WeightDevice {
+					var weight, leafWeight uint16
+					if wd.Weight != nil {
+						weight = *wd.Weight
 					}
-				}
-				if r.BlockIO.ThrottleReadIOPSDevice != nil {
-					for _, td := range r.BlockIO.ThrottleReadIOPSDevice {
-						rate := td.Rate
-						throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
-						c.Resources.BlkioThrottleReadIOPSDevice = append(c.Resources.BlkioThrottleReadIOPSDevice, throttleDevice)
+					if wd.LeafWeight != nil {
+						leafWeight = *wd.LeafWeight
 					}
+					weightDevice := configs.NewWeightDevice(wd.Major, wd.Minor, weight, leafWeight)
+					c.Resources.BlkioWeightDevice = append(c.Resources.BlkioWeightDevice, weightDevice)
 				}
-				if r.BlockIO.ThrottleWriteIOPSDevice != nil {
-					for _, td := range r.BlockIO.ThrottleWriteIOPSDevice {
-						rate := td.Rate
-						throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
-						c.Resources.BlkioThrottleWriteIOPSDevice = append(c.Resources.BlkioThrottleWriteIOPSDevice, throttleDevice)
-					}
+			}
+			if r.BlockIO.ThrottleReadBpsDevice != nil {
+				for _, td := range r.BlockIO.ThrottleReadBpsDevice {
+					rate := td.Rate
+					throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
+					c.Resources.BlkioThrottleReadBpsDevice = append(c.Resources.BlkioThrottleReadBpsDevice, throttleDevice)
 				}
 			}
-			for _, l := range r.HugepageLimits {
-				c.Resources.HugetlbLimit = append(c.Resources.HugetlbLimit, &configs.HugepageLimit{
-					Pagesize: l.Pagesize,
-					Limit:    l.Limit,
-				})
+			if r.BlockIO.ThrottleWriteBpsDevice != nil {
+				for _, td := range r.BlockIO.ThrottleWriteBpsDevice {
+					rate := td.Rate
+					throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
+					c.Resources.BlkioThrottleWriteBpsDevice = append(c.Resources.BlkioThrottleWriteBpsDevice, throttleDevice)
+				}
 			}
-			if r.Network != nil {
-				if r.Network.ClassID != nil {
-					c.Resources.NetClsClassid = *r.Network.ClassID
+			if r.BlockIO.ThrottleReadIOPSDevice != nil {
+				for _, td := range r.BlockIO.ThrottleReadIOPSDevice {
+					rate := td.Rate
+					throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
+					c.Resources.BlkioThrottleReadIOPSDevice = append(c.Resources.BlkioThrottleReadIOPSDevice, throttleDevice)
 				}
-				for _, m := range r.Network.Priorities {
-					c.Resources.NetPrioIfpriomap = append(c.Resources.NetPrioIfpriomap, &configs.IfPrioMap{
-						Interface: m.Name,
-						Priority:  int64(m.Priority),
-					})
+			}
+			if r.BlockIO.ThrottleWriteIOPSDevice != nil {
+				for _, td := range r.BlockIO.ThrottleWriteIOPSDevice {
+					rate := td.Rate
+					throttleDevice := configs.NewThrottleDevice(td.Major, td.Minor, rate)
+					c.Resources.BlkioThrottleWriteIOPSDevice = append(c.Resources.BlkioThrottleWriteIOPSDevice, throttleDevice)
 				}
 			}
 		}
+		for _, l := range r.HugepageLimits {
+			c.Resources.HugetlbLimit = append(c.Resources.HugetlbLimit, &configs.HugepageLimit{
+				Pagesize: l.Pagesize,
+				Limit:    l.Limit,
+			})
+		}
+		if r.Network != nil {
+			if r.Network.ClassID != nil {
+				c.Resources.NetClsClassid = *r.Network.ClassID
+			}
+			for _, m := range r.Network.Priorities {
+				c.Resources.NetPrioIfpriomap = append(c.Resources.NetPrioIfpriomap, &configs.IfPrioMap{
+					Interface: m.Name,
+					Priority:  int64(m.Priority),
+				})
+			}
+		}
 	}
 	// append the default allowed devices to the end of the list
 	c.Resources.Devices = append(c.Resources.Devices, AllowedDevices...)
diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go
index da478c59..4e03b8bc 100644
--- a/libcontainer/standard_init_linux.go
+++ b/libcontainer/standard_init_linux.go
@@ -7,6 +7,7 @@ import (
 	"os"
 	"os/exec"
 	"runtime"
+	"syscall" //only for Exec
 
 	"github.com/opencontainers/runc/libcontainer/apparmor"
 	"github.com/opencontainers/runc/libcontainer/configs"
@@ -206,7 +207,7 @@ func (l *linuxStandardInit) Init() error {
 			return newSystemErrorWithCause(err, "init seccomp")
 		}
 	}
-	if err := unix.Exec(name, l.config.Args[0:], os.Environ()); err != nil {
+	if err := syscall.Exec(name, l.config.Args[0:], os.Environ()); err != nil {
 		return newSystemErrorWithCause(err, "exec user process")
 	}
 	return nil
diff --git a/libcontainer/system/linux.go b/libcontainer/system/linux.go
index 36d72a53..a4ae8901 100644
--- a/libcontainer/system/linux.go
+++ b/libcontainer/system/linux.go
@@ -5,12 +5,26 @@ package system
 import (
 	"os"
 	"os/exec"
+	"syscall" // only for exec
 	"unsafe"
 
 	"github.com/opencontainers/runc/libcontainer/user"
 	"golang.org/x/sys/unix"
 )
 
+// If arg2 is nonzero, set the "child subreaper" attribute of the
+// calling process; if arg2 is zero, unset the attribute.  When a
+// process is marked as a child subreaper, all of the children
+// that it creates, and their descendants, will be marked as
+// having a subreaper.  In effect, a subreaper fulfills the role
+// of init(1) for its descendant processes.  Upon termination of
+// a process that is orphaned (i.e., its immediate parent has
+// already terminated) and marked as having a subreaper, the
+// nearest still living ancestor subreaper will receive a SIGCHLD
+// signal and be able to wait(2) on the process to discover its
+// termination status.
+const PR_SET_CHILD_SUBREAPER = 36
+
 type ParentDeathSignal int
 
 func (p ParentDeathSignal) Restore() error {
@@ -37,7 +51,7 @@ func Execv(cmd string, args []string, env []string) error {
 		return err
 	}
 
-	return unix.Exec(name, args, env)
+	return syscall.Exec(name, args, env)
 }
 
 func Prlimit(pid, resource int, limit unix.Rlimit) error {
@@ -126,7 +140,7 @@ func GetParentNSeuid() int64 {
 
 // SetSubreaper sets the value i as the subreaper setting for the calling process
 func SetSubreaper(i int) error {
-	return unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
+	return unix.Prctl(PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
 }
 
 // GetSubreaper returns the subreaper setting for the calling process
diff --git a/signals.go b/signals.go
index 5682989c..dd25e094 100644
--- a/signals.go
+++ b/signals.go
@@ -5,6 +5,7 @@ package main
 import (
 	"os"
 	"os/signal"
+	"syscall" // only for Signal
 
 	"github.com/opencontainers/runc/libcontainer"
 	"github.com/opencontainers/runc/libcontainer/system"
@@ -102,7 +103,7 @@ func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach
 			}
 		default:
 			logrus.Debugf("sending signal to process %s", s)
-			if err := unix.Kill(pid1, s.(unix.Signal)); err != nil {
+			if err := unix.Kill(pid1, s.(syscall.Signal)); err != nil {
 				logrus.Error(err)
 			}
 		}
diff --git a/types/events.go b/types/events.go
index c6f0e97a..34d170c8 100644
--- a/types/events.go
+++ b/types/events.go
@@ -1,5 +1,7 @@
 package types
 
+import "github.com/opencontainers/runc/libcontainer/intelrdt"
+
 // Event struct for encoding the event data to json.
 type Event struct {
 	Type string      `json:"type"`
@@ -113,6 +115,12 @@ type IntelRdt struct {
 
 	// The memory bandwidth schema in 'container_id' group
 	MemBwSchema string `json:"mem_bw_schema,omitempty"`
+
+	// The memory bandwidth monitoring statistics from NUMA nodes in 'container_id' group
+	MBMStats *[]intelrdt.MBMNumaNodeStats `json:"mbm_stats,omitempty"`
+
+	// The cache monitoring technology statistics from NUMA nodes in 'container_id' group
+	CMTStats *[]intelrdt.CMTNumaNodeStats `json:"cmt_stats,omitempty"`
 }
 
 type NetworkInterface struct {


Test patch:
diff --git a/libcontainer/cgroups/fs/memory_test.go b/libcontainer/cgroups/fs/memory_test.go
index c84b4fe3..62de5637 100644
--- a/libcontainer/cgroups/fs/memory_test.go
+++ b/libcontainer/cgroups/fs/memory_test.go
@@ -18,18 +18,6 @@ rss 1024`
 	memoryFailcnt              = "100\n"
 	memoryLimitContents        = "8192\n"
 	memoryUseHierarchyContents = "1\n"
-	memoryNUMAStatContents     = `total=44611 N0=32631 N1=7501 N2=1982 N3=2497
-file=44428 N0=32614 N1=7335 N2=1982 N3=2497
-anon=183 N0=17 N1=166 N2=0 N3=0
-unevictable=0 N0=0 N1=0 N2=0 N3=0
-hierarchical_total=768133 N0=509113 N1=138887 N2=20464 N3=99669
-hierarchical_file=722017 N0=496516 N1=119997 N2=20181 N3=85323
-hierarchical_anon=46096 N0=12597 N1=18890 N2=283 N3=14326
-hierarchical_unevictable=20 N0=0 N1=0 N2=0 N3=20`
-	memoryNUMAStatNoHierarchyContents = `total=44611 N0=32631 N1=7501 N2=1982 N3=2497
-file=44428 N0=32614 N1=7335 N2=1982 N3=2497
-anon=183 N0=17 N1=166 N2=0 N3=0
-unevictable=0 N0=0 N1=0 N2=0 N3=0`
 )
 
 func TestMemorySetMemory(t *testing.T) {
@@ -288,7 +276,6 @@ func TestMemoryStats(t *testing.T) {
 		"memory.kmem.failcnt":             memoryFailcnt,
 		"memory.kmem.limit_in_bytes":      memoryLimitContents,
 		"memory.use_hierarchy":            memoryUseHierarchyContents,
-		"memory.numa_stat":                memoryNUMAStatContents,
 	})
 
 	memory := &MemoryGroup{}
@@ -297,21 +284,7 @@ func TestMemoryStats(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	expectedStats := cgroups.MemoryStats{Cache: 512, Usage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, SwapUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, KernelUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, Stats: map[string]uint64{"cache": 512, "rss": 1024}, UseHierarchy: true,
-		PageUsageByNUMA: cgroups.PageUsageByNUMA{
-			PageUsageByNUMAInner: cgroups.PageUsageByNUMAInner{
-				Total:       cgroups.PageStats{Total: 44611, Nodes: map[uint8]uint64{0: 32631, 1: 7501, 2: 1982, 3: 2497}},
-				File:        cgroups.PageStats{Total: 44428, Nodes: map[uint8]uint64{0: 32614, 1: 7335, 2: 1982, 3: 2497}},
-				Anon:        cgroups.PageStats{Total: 183, Nodes: map[uint8]uint64{0: 17, 1: 166, 2: 0, 3: 0}},
-				Unevictable: cgroups.PageStats{Total: 0, Nodes: map[uint8]uint64{0: 0, 1: 0, 2: 0, 3: 0}},
-			},
-			Hierarchical: cgroups.PageUsageByNUMAInner{
-				Total:       cgroups.PageStats{Total: 768133, Nodes: map[uint8]uint64{0: 509113, 1: 138887, 2: 20464, 3: 99669}},
-				File:        cgroups.PageStats{Total: 722017, Nodes: map[uint8]uint64{0: 496516, 1: 119997, 2: 20181, 3: 85323}},
-				Anon:        cgroups.PageStats{Total: 46096, Nodes: map[uint8]uint64{0: 12597, 1: 18890, 2: 283, 3: 14326}},
-				Unevictable: cgroups.PageStats{Total: 20, Nodes: map[uint8]uint64{0: 0, 1: 0, 2: 0, 3: 20}},
-			},
-		}}
+	expectedStats := cgroups.MemoryStats{Cache: 512, Usage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, SwapUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, KernelUsage: cgroups.MemoryData{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Limit: 8192}, Stats: map[string]uint64{"cache": 512, "rss": 1024}, UseHierarchy: true}
 	expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats)
 }
 
@@ -481,26 +454,3 @@ func TestMemorySetOomControl(t *testing.T) {
 		t.Fatalf("Got the wrong value, set memory.oom_control failed.")
 	}
 }
-
-func TestNoHierarchicalNumaStat(t *testing.T) {
-	helper := NewCgroupTestUtil("memory", t)
-	defer helper.cleanup()
-	helper.writeFileContents(map[string]string{
-		"memory.numa_stat": memoryNUMAStatNoHierarchyContents,
-	})
-
-	actualStats, err := getPageUsageByNUMA(helper.CgroupPath)
-	if err != nil {
-		t.Fatal(err)
-	}
-	pageUsageByNUMA := cgroups.PageUsageByNUMA{
-		PageUsageByNUMAInner: cgroups.PageUsageByNUMAInner{
-			Total:       cgroups.PageStats{Total: 44611, Nodes: map[uint8]uint64{0: 32631, 1: 7501, 2: 1982, 3: 2497}},
-			File:        cgroups.PageStats{Total: 44428, Nodes: map[uint8]uint64{0: 32614, 1: 7335, 2: 1982, 3: 2497}},
-			Anon:        cgroups.PageStats{Total: 183, Nodes: map[uint8]uint64{0: 17, 1: 166, 2: 0, 3: 0}},
-			Unevictable: cgroups.PageStats{Total: 0, Nodes: map[uint8]uint64{0: 0, 1: 0, 2: 0, 3: 0}},
-		},
-		Hierarchical: cgroups.PageUsageByNUMAInner{},
-	}
-	expectPageUsageByNUMAEquals(t, pageUsageByNUMA, actualStats)
-}
diff --git a/libcontainer/cgroups/fs/stats_util_test.go b/libcontainer/cgroups/fs/stats_util_test.go
index cdce03cf..c5a8d185 100644
--- a/libcontainer/cgroups/fs/stats_util_test.go
+++ b/libcontainer/cgroups/fs/stats_util_test.go
@@ -4,10 +4,11 @@ package fs
 
 import (
 	"fmt"
-	"reflect"
 	"testing"
 
 	"github.com/opencontainers/runc/libcontainer/cgroups"
+
+	"github.com/sirupsen/logrus"
 )
 
 func blkioStatEntryEquals(expected, actual []cgroups.BlkioStatEntry) error {
@@ -25,47 +26,57 @@ func blkioStatEntryEquals(expected, actual []cgroups.BlkioStatEntry) error {
 
 func expectBlkioStatsEquals(t *testing.T, expected, actual cgroups.BlkioStats) {
 	if err := blkioStatEntryEquals(expected.IoServiceBytesRecursive, actual.IoServiceBytesRecursive); err != nil {
-		t.Errorf("blkio IoServiceBytesRecursive do not match - %s\n", err)
+		logrus.Printf("blkio IoServiceBytesRecursive do not match - %s\n", err)
+		t.Fail()
 	}
 
 	if err := blkioStatEntryEquals(expected.IoServicedRecursive, actual.IoServicedRecursive); err != nil {
-		t.Errorf("blkio IoServicedRecursive do not match - %s\n", err)
+		logrus.Printf("blkio IoServicedRecursive do not match - %s\n", err)
+		t.Fail()
 	}
 
 	if err := blkioStatEntryEquals(expected.IoQueuedRecursive, actual.IoQueuedRecursive); err != nil {
-		t.Errorf("blkio IoQueuedRecursive do not match - %s\n", err)
+		logrus.Printf("blkio IoQueuedRecursive do not match - %s\n", err)
+		t.Fail()
 	}
 
 	if err := blkioStatEntryEquals(expected.SectorsRecursive, actual.SectorsRecursive); err != nil {
-		t.Errorf("blkio SectorsRecursive do not match - %s\n", err)
+		logrus.Printf("blkio SectorsRecursive do not match - %s\n", err)
+		t.Fail()
 	}
 
 	if err := blkioStatEntryEquals(expected.IoServiceTimeRecursive, actual.IoServiceTimeRecursive); err != nil {
-		t.Errorf("blkio IoServiceTimeRecursive do not match - %s\n", err)
+		logrus.Printf("blkio IoServiceTimeRecursive do not match - %s\n", err)
+		t.Fail()
 	}
 
 	if err := blkioStatEntryEquals(expected.IoWaitTimeRecursive, actual.IoWaitTimeRecursive); err != nil {
-		t.Errorf("blkio IoWaitTimeRecursive do not match - %s\n", err)
+		logrus.Printf("blkio IoWaitTimeRecursive do not match - %s\n", err)
+		t.Fail()
 	}
 
 	if err := blkioStatEntryEquals(expected.IoMergedRecursive, actual.IoMergedRecursive); err != nil {
-		t.Errorf("blkio IoMergedRecursive do not match - %v vs %v\n", expected.IoMergedRecursive, actual.IoMergedRecursive)
+		logrus.Printf("blkio IoMergedRecursive do not match - %v vs %v\n", expected.IoMergedRecursive, actual.IoMergedRecursive)
+		t.Fail()
 	}
 
 	if err := blkioStatEntryEquals(expected.IoTimeRecursive, actual.IoTimeRecursive); err != nil {
-		t.Errorf("blkio IoTimeRecursive do not match - %s\n", err)
+		logrus.Printf("blkio IoTimeRecursive do not match - %s\n", err)
+		t.Fail()
 	}
 }
 
 func expectThrottlingDataEquals(t *testing.T, expected, actual cgroups.ThrottlingData) {
 	if expected != actual {
-		t.Errorf("Expected throttling data %v but found %v\n", expected, actual)
+		logrus.Printf("Expected throttling data %v but found %v\n", expected, actual)
+		t.Fail()
 	}
 }
 
 func expectHugetlbStatEquals(t *testing.T, expected, actual cgroups.HugetlbStats) {
 	if expected != actual {
-		t.Errorf("Expected hugetlb stats %v but found %v\n", expected, actual)
+		logrus.Printf("Expected hugetlb stats %v but found %v\n", expected, actual)
+		t.Fail()
 	}
 }
 
@@ -73,61 +84,40 @@ func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats)
 	expectMemoryDataEquals(t, expected.Usage, actual.Usage)
 	expectMemoryDataEquals(t, expected.SwapUsage, actual.SwapUsage)
 	expectMemoryDataEquals(t, expected.KernelUsage, actual.KernelUsage)
-	expectPageUsageByNUMAEquals(t, expected.PageUsageByNUMA, actual.PageUsageByNUMA)
 
 	if expected.UseHierarchy != actual.UseHierarchy {
-		t.Errorf("Expected memory use hierarchy %v, but found %v\n", expected.UseHierarchy, actual.UseHierarchy)
+		logrus.Printf("Expected memory use hierarchy %v, but found %v\n", expected.UseHierarchy, actual.UseHierarchy)
+		t.Fail()
 	}
 
 	for key, expValue := range expected.Stats {
 		actValue, ok := actual.Stats[key]
 		if !ok {
-			t.Errorf("Expected memory stat key %s not found\n", key)
+			logrus.Printf("Expected memory stat key %s not found\n", key)
+			t.Fail()
 		}
 		if expValue != actValue {
-			t.Errorf("Expected memory stat value %d but found %d\n", expValue, actValue)
+			logrus.Printf("Expected memory stat value %d but found %d\n", expValue, actValue)
+			t.Fail()
 		}
 	}
 }
 
 func expectMemoryDataEquals(t *testing.T, expected, actual cgroups.MemoryData) {
 	if expected.Usage != actual.Usage {
-		t.Errorf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage)
+		logrus.Printf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage)
+		t.Fail()
 	}
 	if expected.MaxUsage != actual.MaxUsage {
-		t.Errorf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage)
+		logrus.Printf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage)
+		t.Fail()
 	}
 	if expected.Failcnt != actual.Failcnt {
-		t.Errorf("Expected memory failcnt %d but found %d\n", expected.Failcnt, actual.Failcnt)
+		logrus.Printf("Expected memory failcnt %d but found %d\n", expected.Failcnt, actual.Failcnt)
+		t.Fail()
 	}
 	if expected.Limit != actual.Limit {
-		t.Errorf("Expected memory limit %d but found %d\n", expected.Limit, actual.Limit)
-	}
-}
-
-func expectPageUsageByNUMAEquals(t *testing.T, expected, actual cgroups.PageUsageByNUMA) {
-	if !reflect.DeepEqual(expected.Total, actual.Total) {
-		t.Errorf("Expected total page usage by NUMA %#v but found %#v", expected.Total, actual.Total)
-	}
-	if !reflect.DeepEqual(expected.File, actual.File) {
-		t.Errorf("Expected file page usage by NUMA %#v but found %#v", expected.File, actual.File)
-	}
-	if !reflect.DeepEqual(expected.Anon, actual.Anon) {
-		t.Errorf("Expected anon page usage by NUMA %#v but found %#v", expected.Anon, actual.Anon)
-	}
-	if !reflect.DeepEqual(expected.Unevictable, actual.Unevictable) {
-		t.Errorf("Expected unevictable page usage by NUMA %#v but found %#v", expected.Unevictable, actual.Unevictable)
-	}
-	if !reflect.DeepEqual(expected.Hierarchical.Total, actual.Hierarchical.Total) {
-		t.Errorf("Expected hierarchical total page usage by NUMA %#v but found %#v", expected.Hierarchical.Total, actual.Hierarchical.Total)
-	}
-	if !reflect.DeepEqual(expected.Hierarchical.File, actual.Hierarchical.File) {
-		t.Errorf("Expected hierarchical file page usage by NUMA %#v but found %#v", expected.Hierarchical.File, actual.Hierarchical.File)
-	}
-	if !reflect.DeepEqual(expected.Hierarchical.Anon, actual.Hierarchical.Anon) {
-		t.Errorf("Expected hierarchical anon page usage by NUMA %#v but found %#v", expected.Hierarchical.Anon, actual.Hierarchical.Anon)
-	}
-	if !reflect.DeepEqual(expected.Hierarchical.Unevictable, actual.Hierarchical.Unevictable) {
-		t.Errorf("Expected hierarchical total page usage by NUMA %#v but found %#v", expected.Hierarchical.Unevictable, actual.Hierarchical.Unevictable)
+		logrus.Printf("Expected memory limit %d but found %d\n", expected.Limit, actual.Limit)
+		t.Fail()
 	}
 }
diff --git a/libcontainer/integration/checkpoint_test.go b/libcontainer/integration/checkpoint_test.go
index ad34fb36..2f6990f8 100644
--- a/libcontainer/integration/checkpoint_test.go
+++ b/libcontainer/integration/checkpoint_test.go
@@ -11,6 +11,7 @@ import (
 	"testing"
 
 	"github.com/opencontainers/runc/libcontainer"
+	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/configs"
 
 	"golang.org/x/sys/unix"
@@ -79,9 +80,17 @@ func testCheckpoint(t *testing.T, userns bool) {
 		config.GidMappings = []configs.IDMap{{HostID: 0, ContainerID: 0, Size: 1000}}
 		config.Namespaces = append(config.Namespaces, configs.Namespace{Type: configs.NEWUSER})
 	} else {
+		var cgroupDevice string
+
+		if cgroups.IsCgroup2UnifiedMode() {
+			cgroupDevice = "cgroup2"
+		} else {
+			cgroupDevice = "cgroup"
+		}
+
 		config.Mounts = append(config.Mounts, &configs.Mount{
 			Destination: "/sys/fs/cgroup",
-			Device:      "cgroup",
+			Device:      cgroupDevice,
 			Flags:       defaultMountFlags | unix.MS_RDONLY,
 		})
 	}
diff --git a/libcontainer/intelrdt/cmt_test.go b/libcontainer/intelrdt/cmt_test.go
new file mode 100644
index 00000000..e061695e
--- /dev/null
+++ b/libcontainer/intelrdt/cmt_test.go
@@ -0,0 +1,56 @@
+package intelrdt
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+func TestGetCMTNumaNodeStats(t *testing.T) {
+	mocksNUMANodesToCreate := []string{"mon_l3_00", "mon_l3_01"}
+
+	mocksFilesToCreate := map[string]uint64{
+		"llc_occupancy": 9123911,
+	}
+
+	mockedL3_MON, err := mockResctrlL3_MON(mocksNUMANodesToCreate, mocksFilesToCreate)
+
+	defer func() {
+		err := os.RemoveAll(mockedL3_MON)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Run("Gather mbm", func(t *testing.T) {
+		enabledMonFeatures.llcOccupancy = true
+
+		stats := make([]CMTNumaNodeStats, 0, len(mocksNUMANodesToCreate))
+		for _, numa := range mocksNUMANodesToCreate {
+			other, err := getCMTNumaNodeStats(filepath.Join(mockedL3_MON, "mon_data", numa))
+			if err != nil {
+				t.Fatal(err)
+			}
+			stats = append(stats, *other)
+		}
+
+		expectedStats := CMTNumaNodeStats{
+			LLCOccupancy: mocksFilesToCreate["llc_occupancy"],
+		}
+
+		checkCMTStatCorrection(stats[0], expectedStats, t)
+		checkCMTStatCorrection(stats[1], expectedStats, t)
+	})
+}
+
+func checkCMTStatCorrection(got CMTNumaNodeStats, expected CMTNumaNodeStats, t *testing.T) {
+	if got.LLCOccupancy != expected.LLCOccupancy {
+		t.Fatalf("Wrong value of `llc_occupancy`. Expected: %v but got: %v",
+			expected.LLCOccupancy,
+			got.LLCOccupancy)
+	}
+}
diff --git a/libcontainer/intelrdt/mbm_test.go b/libcontainer/intelrdt/mbm_test.go
new file mode 100644
index 00000000..ae0f202c
--- /dev/null
+++ b/libcontainer/intelrdt/mbm_test.go
@@ -0,0 +1,68 @@
+// +build linux
+
+package intelrdt
+
+import (
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+func TestGetMBMNumaNodeStats(t *testing.T) {
+	mocksNUMANodesToCreate := []string{"mon_l3_00", "mon_l3_01"}
+
+	mocksFilesToCreate := map[string]uint64{
+		"mbm_total_bytes": 9123911,
+		"mbm_local_bytes": 2361361,
+	}
+
+	mockedL3_MON, err := mockResctrlL3_MON(mocksNUMANodesToCreate, mocksFilesToCreate)
+
+	defer func() {
+		err := os.RemoveAll(mockedL3_MON)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Run("Gather mbm", func(t *testing.T) {
+		enabledMonFeatures.mbmTotalBytes = true
+		enabledMonFeatures.mbmLocalBytes = true
+
+		stats := make([]MBMNumaNodeStats, 0, len(mocksNUMANodesToCreate))
+		for _, numa := range mocksNUMANodesToCreate {
+			other, err := getMBMNumaNodeStats(filepath.Join(mockedL3_MON, "mon_data", numa))
+			if err != nil {
+				t.Fatal(err)
+			}
+			stats = append(stats, *other)
+		}
+
+		expectedStats := MBMNumaNodeStats{
+			MBMTotalBytes: mocksFilesToCreate["mbm_total_bytes"],
+			MBMLocalBytes: mocksFilesToCreate["mbm_local_bytes"],
+		}
+
+		checkMBMStatCorrection(stats[0], expectedStats, t)
+		checkMBMStatCorrection(stats[1], expectedStats, t)
+	})
+}
+
+func checkMBMStatCorrection(got MBMNumaNodeStats, expected MBMNumaNodeStats, t *testing.T) {
+	if got.MBMTotalBytes != expected.MBMTotalBytes {
+		t.Fatalf("Wrong value of mbm_total_bytes. Expected: %v but got: %v",
+			expected.MBMTotalBytes,
+			got.MBMTotalBytes)
+	}
+
+	if got.MBMLocalBytes != expected.MBMLocalBytes {
+		t.Fatalf("Wrong value of mbm_local_bytes. Expected: %v but got: %v",
+			expected.MBMLocalBytes,
+			got.MBMLocalBytes)
+	}
+
+}
diff --git a/libcontainer/intelrdt/monitoring_test.go b/libcontainer/intelrdt/monitoring_test.go
new file mode 100644
index 00000000..ec886a7e
--- /dev/null
+++ b/libcontainer/intelrdt/monitoring_test.go
@@ -0,0 +1,118 @@
+package intelrdt
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"testing"
+)
+
+func TestParseMonFeatures(t *testing.T) {
+	t.Run("All features available", func(t *testing.T) {
+		parsedMonFeatures, err := parseMonFeatures(
+			strings.NewReader("mbm_total_bytes\nmbm_local_bytes\nllc_occupancy"))
+		if err != nil {
+			t.Errorf("Error while parsing mon features err = %v", err)
+		}
+
+		expectedMonFeatures := monFeatures{true, true, true}
+
+		if parsedMonFeatures != expectedMonFeatures {
+			t.Error("Cannot gather all features!")
+		}
+	})
+
+	t.Run("No features available", func(t *testing.T) {
+		parsedMonFeatures, err := parseMonFeatures(strings.NewReader(""))
+
+		if err != nil {
+			t.Errorf("Error while parsing mon features err = %v", err)
+		}
+
+		expectedMonFeatures := monFeatures{false, false, false}
+
+		if parsedMonFeatures != expectedMonFeatures {
+			t.Error("Expected no features available but there is any!")
+		}
+	})
+}
+
+func mockResctrlL3_MON(NUMANodes []string, mocks map[string]uint64) (string, error) {
+	testDir, err := ioutil.TempDir("", "rdt_mbm_test")
+	if err != nil {
+		return "", err
+	}
+	monDataPath := filepath.Join(testDir, "mon_data")
+
+	for _, numa := range NUMANodes {
+		numaPath := filepath.Join(monDataPath, numa)
+		err = os.MkdirAll(numaPath, os.ModePerm)
+		if err != nil {
+			return "", err
+		}
+
+		for fileName, value := range mocks {
+			err := ioutil.WriteFile(filepath.Join(numaPath, fileName), []byte(strconv.FormatUint(value, 10)), 777)
+			if err != nil {
+				return "", err
+			}
+		}
+
+	}
+
+	return testDir, nil
+}
+
+func TestGetMonitoringStats(t *testing.T) {
+	enabledMonFeatures.mbmTotalBytes = true
+	enabledMonFeatures.mbmLocalBytes = true
+	enabledMonFeatures.llcOccupancy = true
+	mbmEnabled = true
+	cmtEnabled = true
+
+	mocksNUMANodesToCreate := []string{"mon_l3_00", "mon_l3_01"}
+
+	mocksFilesToCreate := map[string]uint64{
+		"mbm_total_bytes": 9123911,
+		"mbm_local_bytes": 2361361,
+		"llc_occupancy":   123331,
+	}
+
+	mockedL3_MON, err := mockResctrlL3_MON(mocksNUMANodesToCreate, mocksFilesToCreate)
+
+	defer func() {
+		err := os.RemoveAll(mockedL3_MON)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	t.Run("Gather monitoring stats", func(t *testing.T) {
+		var stats Stats
+		err := getMonitoringStats(mockedL3_MON, &stats)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		expectedMBMStats := MBMNumaNodeStats{
+			MBMTotalBytes: mocksFilesToCreate["mbm_total_bytes"],
+			MBMLocalBytes: mocksFilesToCreate["mbm_local_bytes"],
+		}
+
+		expectedCMTStats := CMTNumaNodeStats{LLCOccupancy: mocksFilesToCreate["llc_occupancy"]}
+
+		for _, gotMBMStat := range *stats.MBMStats {
+			checkMBMStatCorrection(gotMBMStat, expectedMBMStats, t)
+		}
+
+		for _, gotCMTStat := range *stats.CMTStats {
+			checkCMTStatCorrection(gotCMTStat, expectedCMTStats, t)
+		}
+	})
+}
diff --git a/tests/integration/cgroups.bats b/tests/integration/cgroups.bats
index 6abb2ed6..17812abf 100644
--- a/tests/integration/cgroups.bats
+++ b/tests/integration/cgroups.bats
@@ -14,7 +14,18 @@ function setup() {
     setup_busybox
 }
 
-@test "runc update --kernel-memory{,-tcp} (initialized)" {
+function check_cgroup_value() {
+    cgroup=$1
+    source=$2
+    expected=$3
+
+    current=$(cat $cgroup/$source)
+    echo  $cgroup/$source
+    echo "current" $current "!?" "$expected"
+    [ "$current" -eq "$expected" ]
+}
+
+@test "runc update --kernel-memory (initialized)" {
     [[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
     requires cgroups_kmem
 
@@ -23,8 +34,7 @@ function setup() {
     # Set some initial known values
     DATA=$(cat <<-EOF
     "memory": {
-        "kernel": 16777216,
-        "kernelTCP": 11534336
+        "kernel": 16777216
     },
 EOF
     )
@@ -35,18 +45,12 @@ EOF
     runc run -d --console-socket $CONSOLE_SOCKET test_cgroups_kmem
     [ "$status" -eq 0 ]
 
-    check_cgroup_value "memory.kmem.limit_in_bytes" 16777216
-    check_cgroup_value "memory.kmem.tcp.limit_in_bytes" 11534336
-
     # update kernel memory limit
     runc update test_cgroups_kmem --kernel-memory 50331648
     [ "$status" -eq 0 ]
-    check_cgroup_value "memory.kmem.limit_in_bytes" 50331648
 
-    # update kernel memory tcp limit
-    runc update test_cgroups_kmem --kernel-memory-tcp 41943040
-    [ "$status" -eq 0 ]
-    check_cgroup_value "memory.kmem.tcp.limit_in_bytes" 41943040
+	# check the value
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.limit_in_bytes" 50331648
 }
 
 @test "runc update --kernel-memory (uninitialized)" {
@@ -67,7 +71,7 @@ EOF
         [ ! "$status" -eq 0 ]
     else
         [ "$status" -eq 0 ]
-        check_cgroup_value "memory.kmem.limit_in_bytes" 50331648
+        check_cgroup_value $CGROUP_MEMORY "memory.kmem.limit_in_bytes" 50331648
     fi
 }
 
diff --git a/tests/integration/checkpoint.bats b/tests/integration/checkpoint.bats
index 154e93c5..87696df6 100644
--- a/tests/integration/checkpoint.bats
+++ b/tests/integration/checkpoint.bats
@@ -3,8 +3,9 @@
 load helpers
 
 function setup() {
-  # XXX: currently criu require root containers.
-  requires criu root
+  if [[ -n "${RUNC_USE_SYSTEMD}" ]] ; then
+    skip "CRIU test suite is skipped on systemd cgroup driver for now."
+  fi
 
   teardown_busybox
   setup_busybox
@@ -14,34 +15,10 @@ function teardown() {
   teardown_busybox
 }
 
-function setup_pipes() {
-	# The changes to 'terminal' are needed for running in detached mode
-	sed -i 's;"terminal": true;"terminal": false;' config.json
-	sed -i 's/"sh"/"sh","-c","for i in `seq 10`; do read xxx || continue; echo ponG $xxx; done"/' config.json
-
-	# Create two sets of pipes
-	# for stdout/stderr
-	exec 52<> <(:)
-	exec 50</proc/self/fd/52
-	exec 51>/proc/self/fd/52
-	exec 52>&-
-	# ... and stdin
-	exec 62<> <(:)
-	exec 60</proc/self/fd/62
-	exec 61>/proc/self/fd/62
-	exec 62>&-
-}
-
-function check_pipes() {
-	echo Ping >&61
-	exec 61>&-
-	exec 51>&-
-	run cat <&50
-	[ "$status" -eq 0 ]
-	[[ "${output}" == *"ponG Ping"* ]]
-}
+@test "checkpoint and restore" {
+  # XXX: currently criu require root containers.
+  requires criu root
 
-function simple_cr() {
   runc run -d --console-socket $CONSOLE_SOCKET test_busybox
   [ "$status" -eq 0 ]
 
@@ -50,11 +27,15 @@ function simple_cr() {
   for i in `seq 2`; do
     # checkpoint the running container
     runc --criu "$CRIU" checkpoint --work-path ./work-dir test_busybox
+    ret=$?
+    # if you are having problems getting criu to work uncomment the following dump:
+    #cat /run/opencontainer/containers/test_busybox/criu.work/dump.log
     cat ./work-dir/dump.log | grep -B 5 Error || true
-    [ "$status" -eq 0 ]
+    [ "$ret" -eq 0 ]
 
     # after checkpoint busybox is no longer running
-    testcontainer test_busybox checkpointed
+    runc state test_busybox
+    [ "$status" -ne 0 ]
 
     # restore from checkpoint
     runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket $CONSOLE_SOCKET test_busybox
@@ -67,22 +48,33 @@ function simple_cr() {
   done
 }
 
-@test "checkpoint and restore " {
-	simple_cr
-}
+@test "checkpoint --pre-dump and restore" {
+  # XXX: currently criu require root containers.
+  requires criu root
 
-@test "checkpoint and restore (cgroupns)" {
-  # cgroupv2 already enables cgroupns so this case was tested above already
-  requires cgroups_v1
+  # The changes to 'terminal' are needed for running in detached mode
+  sed -i 's;"terminal": true;"terminal": false;' config.json
+  sed -i 's/"sh"/"sh","-c","for i in `seq 10`; do read xxx || continue; echo ponG $xxx; done"/' config.json
 
-  # enable CGROUPNS
-  sed -i 's|\("namespaces": \[\)|\1\n\t\t\t{"type": "cgroup"},|' config.json
+  # The following code creates pipes for stdin and stdout.
+  # CRIU can't handle fifo-s, so we need all these tricks.
+  fifo=`mktemp -u /tmp/runc-fifo-XXXXXX`
+  mkfifo $fifo
 
-  simple_cr
-}
+  # stdout
+  cat $fifo | cat $fifo &
+  pid=$!
+  exec 50</proc/$pid/fd/0
+  exec 51>/proc/$pid/fd/0
 
-@test "checkpoint --pre-dump and restore" {
-  setup_pipes
+  # stdin
+  cat $fifo | cat $fifo &
+  pid=$!
+  exec 60</proc/$pid/fd/0
+  exec 61>/proc/$pid/fd/0
+
+  echo -n > $fifo
+  unlink $fifo
 
   # run busybox
   __runc run -d test_busybox <&60 >&51 2>&51
@@ -96,7 +88,9 @@ function simple_cr() {
   [ "$status" -eq 0 ]
 
   # busybox should still be running
-  testcontainer test_busybox running
+  runc state test_busybox
+  [ "$status" -eq 0 ]
+  [[ "${output}" == *"running"* ]]
 
   # checkpoint the running container
   mkdir image-dir
@@ -106,7 +100,8 @@ function simple_cr() {
   [ "$status" -eq 0 ]
 
   # after checkpoint busybox is no longer running
-  testcontainer test_busybox checkpointed
+  runc state test_busybox
+  [ "$status" -ne 0 ]
 
   # restore from checkpoint
   __runc --criu "$CRIU" restore -d --work-path ./work-dir --image-path ./image-dir test_busybox <&60 >&51 2>&51
@@ -121,20 +116,35 @@ function simple_cr() {
   [ "$status" -eq 0 ]
   [[ ${output} == "ok" ]]
 
-  check_pipes
+  echo Ping >&61
+  exec 61>&-
+  exec 51>&-
+  run cat <&50
+  [ "$status" -eq 0 ]
+  [[ "${output}" == *"ponG Ping"* ]]
 }
 
 @test "checkpoint --lazy-pages and restore" {
+  # XXX: currently criu require root containers.
+  requires criu root
+
   # check if lazy-pages is supported
   run ${CRIU} check --feature uffd-noncoop
   if [ "$status" -eq 1 ]; then
+    # this criu does not support lazy migration; skip the test
     skip "this criu does not support lazy migration"
   fi
 
-  setup_pipes
-
+  # The changes to 'terminal' are needed for running in detached mode
+  sed -i 's;"terminal": true;"terminal": false;' config.json
   # This should not be necessary: https://github.com/checkpoint-restore/criu/issues/575
   sed -i 's;"readonly": true;"readonly": false;' config.json
+  sed -i 's/"sh"/"sh","-c","for i in `seq 10`; do read xxx || continue; echo ponG $xxx; done"/' config.json
+
+  # The following code creates pipes for stdin and stdout.
+  # CRIU can't handle fifo-s, so we need all these tricks.
+  fifo=`mktemp -u /tmp/runc-fifo-XXXXXX`
+  mkfifo $fifo
 
   # For lazy migration we need to know when CRIU is ready to serve
   # the memory pages via TCP.
@@ -144,6 +154,21 @@ function simple_cr() {
   # TCP port for lazy migration
   port=27277
 
+  # stdout
+  cat $fifo | cat $fifo &
+  pid=$!
+  exec 50</proc/$pid/fd/0
+  exec 51>/proc/$pid/fd/0
+
+  # stdin
+  cat $fifo | cat $fifo &
+  pid=$!
+  exec 60</proc/$pid/fd/0
+  exec 61>/proc/$pid/fd/0
+
+  echo -n > $fifo
+  unlink $fifo
+
   # run busybox
   __runc run -d test_busybox <&60 >&51 2>&51
   [ $? -eq 0 ]
@@ -189,7 +214,8 @@ function simple_cr() {
   # Killing the CRIU on the checkpoint side will let the container
   # continue to run if the migration failed at some point.
   __runc --criu "$CRIU" restore -d --work-path ./image-dir --image-path ./image-dir --lazy-pages test_busybox_restore <&60 >&51 2>&51
-  [ $? -eq 0 ]
+  ret=$?
+  [ $ret -eq 0 ]
   run grep -B 5 Error ./work-dir/dump.log -q
   [ "$status" -eq 1 ]
 
@@ -200,10 +226,18 @@ function simple_cr() {
   [ "$status" -eq 0 ]
   [[ ${output} == "ok" ]]
 
-  check_pipes
+  echo Ping >&61
+  exec 61>&-
+  exec 51>&-
+  run cat <&50
+  [ "$status" -eq 0 ]
+  [[ "${output}" == *"ponG Ping"* ]]
 }
 
 @test "checkpoint and restore in external network namespace" {
+  # XXX: currently criu require root containers.
+  requires criu root
+
   # check if external_net_ns is supported; only with criu 3.10++
   run ${CRIU} check --feature external_net_ns
   if [ "$status" -eq 1 ]; then
@@ -233,13 +267,15 @@ function simple_cr() {
     # checkpoint the running container; this automatically tells CRIU to
     # handle the network namespace defined in config.json as an external
     runc --criu "$CRIU" checkpoint --work-path ./work-dir test_busybox
+    ret=$?
     # if you are having problems getting criu to work uncomment the following dump:
     #cat /run/opencontainer/containers/test_busybox/criu.work/dump.log
     cat ./work-dir/dump.log | grep -B 5 Error || true
-    [ "$status" -eq 0 ]
+    [ "$ret" -eq 0 ]
 
     # after checkpoint busybox is no longer running
-    testcontainer test_busybox checkpointed
+    runc state test_busybox
+    [ "$status" -ne 0 ]
 
     # restore from checkpoint; this should restore the container into the existing network namespace
     runc --criu "$CRIU" restore -d --work-path ./work-dir --console-socket $CONSOLE_SOCKET test_busybox
@@ -261,6 +297,9 @@ function simple_cr() {
 }
 
 @test "checkpoint and restore with container specific CRIU config" {
+  # XXX: currently criu require root containers.
+  requires criu root
+
   tmp=`mktemp /tmp/runc-criu-XXXXXX.conf`
   # This is the file we write to /etc/criu/default.conf
   tmplog1=`mktemp /tmp/runc-criu-log-XXXXXX.log`
@@ -291,7 +330,8 @@ function simple_cr() {
   test -f ./work-dir/$tmplog2
 
   # after checkpoint busybox is no longer running
-  testcontainer test_busybox checkpointed
+  runc state test_busybox
+  [ "$status" -ne 0 ]
 
   test -f ./work-dir/$tmplog2 && unlink ./work-dir/$tmplog2
   # restore from checkpoint
diff --git a/tests/integration/events.bats b/tests/integration/events.bats
index 8f2f81a2..b3e6315b 100644
--- a/tests/integration/events.bats
+++ b/tests/integration/events.bats
@@ -13,8 +13,7 @@ function teardown() {
 
 @test "events --stats" {
   # XXX: currently cgroups require root containers.
-  # TODO: support cgroup v2 memory.events
-  requires root cgroups_v1
+  requires root
 
   # run busybox detached
   runc run -d --console-socket $CONSOLE_SOCKET test_busybox
@@ -29,8 +28,7 @@ function teardown() {
 
 @test "events --interval default " {
   # XXX: currently cgroups require root containers.
-  # TODO: support cgroup v2 memory.events
-  requires root cgroups_v1
+  requires root
 
   # run busybox detached
   runc run -d --console-socket $CONSOLE_SOCKET test_busybox
@@ -57,8 +55,7 @@ function teardown() {
 
 @test "events --interval 1s " {
   # XXX: currently cgroups require root containers.
-  # TODO: support cgroup v2 memory.events
-  requires root cgroups_v1
+  requires root
 
   # run busybox detached
   runc run -d --console-socket $CONSOLE_SOCKET test_busybox
@@ -84,8 +81,7 @@ function teardown() {
 
 @test "events --interval 100ms " {
   # XXX: currently cgroups require root containers.
-  # TODO: support cgroup v2 memory.events
-  requires root cgroups_v1
+  requires root
 
   # run busybox detached
   runc run -d --console-socket $CONSOLE_SOCKET test_busybox
diff --git a/tests/integration/helpers.bash b/tests/integration/helpers.bash
index 944affdc..8862dcb8 100644
--- a/tests/integration/helpers.bash
+++ b/tests/integration/helpers.bash
@@ -36,6 +36,20 @@ ROOT=$(mktemp -d "$BATS_TMPDIR/runc.XXXXXX")
 # Path to console socket.
 CONSOLE_SOCKET="$BATS_TMPDIR/console.sock"
 
+# Cgroup paths
+CGROUP_MEMORY_BASE_PATH=$(grep "cgroup" /proc/self/mountinfo | gawk 'toupper($NF) ~ /\<MEMORY\>/ { print $5; exit }')
+CGROUP_CPU_BASE_PATH=$(grep "cgroup" /proc/self/mountinfo | gawk 'toupper($NF) ~ /\<CPU\>/ { print $5; exit }')
+if [[ -n "${RUNC_USE_SYSTEMD}" ]] ; then
+	CGROUPS_PATH="/machine.slice/runc-cgroups-integration-test.scope"
+else
+	CGROUPS_PATH="/runc-cgroups-integration-test/test-cgroup"
+fi
+CGROUP_MEMORY="${CGROUP_MEMORY_BASE_PATH}${CGROUPS_PATH}"
+
+# CONFIG_MEMCG_KMEM support
+KMEM="${CGROUP_MEMORY_BASE_PATH}/memory.kmem.limit_in_bytes"
+RT_PERIOD="${CGROUP_CPU_BASE_PATH}/cpu.rt_period_us"
+
 # Check if we're in rootless mode.
 ROOTLESS=$(id -u)
 
@@ -105,73 +119,14 @@ function runc_rootless_cgroup() {
 	mv "$bundle/config.json"{.tmp,}
 }
 
-function init_cgroup_paths() {
-	# init once
-	test -n "$CGROUP_UNIFIED" && return
-
-	if [ -n "${RUNC_USE_SYSTEMD}" ] ; then
-		REL_CGROUPS_PATH="/machine.slice/runc-cgroups-integration-test.scope"
-		OCI_CGROUPS_PATH="machine.slice:runc-cgroups:integration-test"
-	else
-		REL_CGROUPS_PATH="/runc-cgroups-integration-test/test-cgroup"
-		OCI_CGROUPS_PATH=$REL_CGROUPS_PATH
-	fi
-
-	if stat -f -c %t /sys/fs/cgroup | grep -qFw 63677270; then
-		CGROUP_UNIFIED=yes
-		# "pseudo" controllers do not appear in /sys/fs/cgroup/cgroup.controllers.
-		# - devices (since kernel 4.15)
-		# - freezer (since kernel 5.2)
-		# Assume these are always available, as it is hard to detect
-		CGROUP_SUBSYSTEMS=$(cat /sys/fs/cgroup/cgroup.controllers; echo devices freezer)
-		CGROUP_BASE_PATH=/sys/fs/cgroup
-		CGROUP_PATH=${CGROUP_BASE_PATH}${REL_CGROUPS_PATH}
-	else
-		CGROUP_UNIFIED=no
-		CGROUP_SUBSYSTEMS=$(awk '!/^#/ {print $1}' /proc/cgroups)
-		for g in ${CGROUP_SUBSYSTEMS}; do
-			base_path=$(gawk '$(NF-2) == "cgroup" && $NF ~ /\<'${g}'\>/ { print $5; exit }' /proc/self/mountinfo)
-			test -z "$base_path" && continue
-			eval CGROUP_${g^^}_BASE_PATH="${base_path}"
-			eval CGROUP_${g^^}="${base_path}${REL_CGROUPS_PATH}"
-		done
-	fi
-}
-
-# Helper function to set cgroupsPath to the value of $OCI_CGROUPS_PATH
+# Helper function to set cgroupsPath to the value of $CGROUPS_PATH
 function set_cgroups_path() {
   bundle="${1:-.}"
-  init_cgroup_paths
-  sed -i 's#\("linux": {\)#\1\n    "cgroupsPath": "'"${OCI_CGROUPS_PATH}"'",#' "$bundle/config.json"
-}
-
-# Helper to check a value in cgroups.
-function check_cgroup_value() {
-	source=$1
-	expected=$2
-
-	if [ "x$CGROUP_UNIFIED" = "xyes" ] ; then
-		cgroup=$CGROUP_PATH
-	else
-		ctrl=${source%%.*}
-		eval cgroup=\$CGROUP_${ctrl^^}
-	fi
-
-	current=$(cat $cgroup/$source)
-	echo $cgroup/$source
-	echo "current" $current "!?" "$expected"
-	[ "$current" = "$expected" ]
-}
-
-# Helper to check a value in systemd.
-function check_systemd_value() {
-	unitname=$1
-	source=$2
-	expected=$3
-	
-	current=$(systemctl show $unitname | grep $source)
-	echo "current" $current "!?" "$expected"
-	[ "$current" = "$expected" ]
+  cgroups_path="/runc-cgroups-integration-test/test-cgroup"
+  if [[ -n "${RUNC_USE_SYSTEMD}" ]] ; then
+    cgroups_path="machine.slice:runc-cgroups:integration-test"
+  fi
+  sed -i 's#\("linux": {\)#\1\n    "cgroupsPath": "'"${cgroups_path}"'",#' "$bundle/config.json"
 }
 
 # Helper function to set a resources limit
@@ -222,29 +177,15 @@ function requires() {
 			fi
 			;;
 		cgroups_kmem)
-			init_cgroup_paths
-			if [ ! -e "${CGROUP_MEMORY_BASE_PATH}/memory.kmem.limit_in_bytes" ]; then
+			if [ ! -e "$KMEM" ]; then
 				skip "Test requires ${var}"
 			fi
 			;;
 		cgroups_rt)
-			init_cgroup_paths
-			if [ ! -e "${CGROUP_CPU_BASE_PATH}/cpu.rt_period_us" ]; then
+			if [ ! -e "$RT_PERIOD" ]; then
 				skip "Test requires ${var}"
 			fi
 			;;
-		cgroups_v1)
-			init_cgroup_paths
-			if [ "$CGROUP_UNIFIED" != "no" ]; then
-				skip "Test requires cgroups v1"
-			fi
-			;;
-		cgroups_v2)
-			init_cgroup_paths
-			if [ "$CGROUP_UNIFIED" != "yes" ]; then
-				skip "Test requires cgroups v2 (unified)"
-			fi
-			;;
 		*)
 			fail "BUG: Invalid requires ${var}."
 			;;
@@ -321,10 +262,6 @@ function wait_for_container_inroot() {
 function testcontainer() {
 	# test state of container
 	runc state $1
-	if [ $2 == "checkpointed" ]; then
-		[ "$status" -eq 1 ]
-		return
-	fi
 	[ "$status" -eq 0 ]
 	[[ "${output}" == *"$2"* ]]
 }
diff --git a/tests/integration/tty.bats b/tests/integration/tty.bats
index 47815ee2..688875df 100644
--- a/tests/integration/tty.bats
+++ b/tests/integration/tty.bats
@@ -68,7 +68,7 @@ function teardown() {
 	testcontainer test_busybox running
 
 	# run the exec
-    runc exec -t test_busybox sh -c 'for file in /proc/self/fd/[012]; do readlink $file; done'
+    runc exec test_busybox sh -c 'for file in /proc/self/fd/[012]; do readlink $file; done'
 	[ "$status" -eq 0 ]
 	[[ ${lines[0]} =~ /dev/pts/+ ]]
 	[[ ${lines[1]} =~ /dev/pts/+ ]]
@@ -88,7 +88,7 @@ function teardown() {
 	testcontainer test_busybox running
 
 	# run the exec
-    runc exec -t test_busybox sh -c 'stat -c %u:%g $(tty) | tr : \\n'
+    runc exec test_busybox sh -c 'stat -c %u:%g $(tty) | tr : \\n'
 	[ "$status" -eq 0 ]
 	[[ ${lines[0]} =~ 0 ]]
 	[[ ${lines[1]} =~ 5 ]]
@@ -111,7 +111,7 @@ function teardown() {
 	testcontainer test_busybox running
 
 	# run the exec
-	runc exec -t test_busybox sh -c 'stat -c %u:%g $(tty) | tr : \\n'
+	runc exec test_busybox sh -c 'stat -c %u:%g $(tty) | tr : \\n'
 	[ "$status" -eq 0 ]
 	[[ ${lines[0]} =~ 1000 ]]
 	[[ ${lines[1]} =~ 5 ]]
@@ -146,7 +146,7 @@ EOF
 	)
 
 	# run the exec
-	runc exec -t --pid-file pid.txt -d --console-socket $CONSOLE_SOCKET -p <( echo $tty_info_with_consize_size ) test_busybox
+	runc exec --pid-file pid.txt -d --console-socket $CONSOLE_SOCKET -p <( echo $tty_info_with_consize_size ) test_busybox
 	[ "$status" -eq 0 ]
 
 	# check the pid was generated
@@ -167,7 +167,7 @@ EOF
 	)
 
 	# run the exec
-	runc exec -t -p <( echo $tty_info ) test_busybox
+	runc exec -p <( echo $tty_info ) test_busybox
 	[ "$status" -eq 0 ]
 
 	# test tty width and height against original process.json
diff --git a/tests/integration/update.bats b/tests/integration/update.bats
index a097549c..549b2ece 100644
--- a/tests/integration/update.bats
+++ b/tests/integration/update.bats
@@ -19,7 +19,9 @@ function setup() {
     DATA=$(cat <<EOF
     "memory": {
         "limit": 33554432,
-        "reservation": 25165824
+        "reservation": 25165824,
+        "kernel": 16777216,
+        "kernelTCP": 11534336
     },
     "cpu": {
         "shares": 100,
@@ -36,117 +38,134 @@ EOF
     sed -i "s/\(\"resources\": {\)/\1\n${DATA}/" ${BUSYBOX_BUNDLE}/config.json
 }
 
-# Tests whatever limits are (more or less) common between cgroup
-# v1 and v2: memory/swap, pids, and cpuset.
-@test "update cgroup v1/v2 common limits" {
+function check_cgroup_value() {
+    cgroup=$1
+    source=$2
+    expected=$3
+
+    current=$(cat $cgroup/$source)
+    [ "$current" == "$expected" ]
+}
+
+# TODO: test rt cgroup updating
+@test "update" {
+    # XXX: Also, this test should be split into separate sections so that we
+    #      can skip kmem without skipping update tests overall.
     [[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
-    init_cgroup_paths
+    requires cgroups_kmem
 
     # run a few busyboxes detached
     runc run -d --console-socket $CONSOLE_SOCKET test_update
     [ "$status" -eq 0 ]
 
-    # Set a few variables to make the code below work for both v1 and v2
-    case $CGROUP_UNIFIED in
-    no)
-        MEM_LIMIT="memory.limit_in_bytes"
-        MEM_RESERVE="memory.soft_limit_in_bytes"
-        MEM_SWAP="memory.memsw.limit_in_bytes"
-        SYSTEM_MEM=$(cat "${CGROUP_MEMORY_BASE_PATH}/${MEM_LIMIT}")
-        SYSTEM_MEM_SWAP=$(cat "${CGROUP_MEMORY_BASE_PATH}/$MEM_SWAP")
-        ;;
-    yes)
-        MEM_LIMIT="memory.max"
-        MEM_RESERVE="memory.low"
-        MEM_SWAP="memory.swap.max"
-        SYSTEM_MEM="max"
-        SYSTEM_MEM_SWAP="max"
-        # checking swap is currently disabled for v2
-        #CGROUP_MEMORY=$CGROUP_PATH
-        ;;
-    esac
+    # get the cgroup paths
+    for g in MEMORY CPUSET CPU BLKIO PIDS; do
+        base_path=$(grep "cgroup"  /proc/self/mountinfo | gawk 'toupper($NF) ~ /\<'${g}'\>/ { print $5; exit }')
+        eval CGROUP_${g}="${base_path}${CGROUPS_PATH}"
+    done
+
+    CGROUP_SYSTEM_MEMORY=$(grep "cgroup"  /proc/self/mountinfo | gawk 'toupper($NF) ~ /\<'MEMORY'\>/ { print $5; exit }')
 
     # check that initial values were properly set
-    check_cgroup_value "cpuset.cpus" 0
-    check_cgroup_value $MEM_LIMIT 33554432
-    check_cgroup_value $MEM_RESERVE 25165824
-    check_cgroup_value "pids.max" 20
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_period_us" 1000000
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_quota_us" 500000
+    check_cgroup_value $CGROUP_CPU "cpu.shares" 100
+    check_cgroup_value $CGROUP_CPUSET "cpuset.cpus" 0
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.limit_in_bytes" 16777216
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.tcp.limit_in_bytes" 11534336
+    check_cgroup_value $CGROUP_MEMORY "memory.limit_in_bytes" 33554432
+    check_cgroup_value $CGROUP_MEMORY "memory.soft_limit_in_bytes" 25165824
+    check_cgroup_value $CGROUP_PIDS "pids.max" 20
+
+    # update cpu-period
+    runc update test_update --cpu-period 900000
+    [ "$status" -eq 0 ]
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_period_us" 900000
+
+    # update cpu-quota
+    runc update test_update --cpu-quota 600000
+    [ "$status" -eq 0 ]
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_quota_us" 600000
+
+    # update cpu-shares
+    runc update test_update --cpu-share 200
+    [ "$status" -eq 0 ]
+    check_cgroup_value $CGROUP_CPU "cpu.shares" 200
 
     # update cpuset if supported (i.e. we're running on a multicore cpu)
-    cpu_count=$(grep -c '^processor' /proc/cpuinfo)
+    cpu_count=$(grep '^processor' /proc/cpuinfo | wc -l)
     if [ $cpu_count -gt 1 ]; then
         runc update test_update --cpuset-cpus "1"
         [ "$status" -eq 0 ]
-        check_cgroup_value "cpuset.cpus" 1
+        check_cgroup_value $CGROUP_CPUSET "cpuset.cpus" 1
     fi
 
     # update memory limit
     runc update test_update --memory 67108864
     [ "$status" -eq 0 ]
-    check_cgroup_value $MEM_LIMIT 67108864
-    if [[ -n "${RUNC_USE_SYSTEMD}" ]] ; then
-        if [ "$CGROUP_UNIFIED" != "yes" ]; then
-            check_systemd_value "runc-cgroups-integration-test.scope" "MemoryLimit=" "MemoryLimit=67108864"
-        else
-            check_systemd_value "runc-cgroups-integration-test.scope" "MemoryMax=" "MemoryMax=67108864"
-        fi
-    fi
+    check_cgroup_value $CGROUP_MEMORY "memory.limit_in_bytes" 67108864
 
     runc update test_update --memory 50M
     [ "$status" -eq 0 ]
-    check_cgroup_value $MEM_LIMIT 52428800
-    if [[ -n "${RUNC_USE_SYSTEMD}" ]] ; then
-        if [ "$CGROUP_UNIFIED" != "yes" ]; then
-            check_systemd_value "runc-cgroups-integration-test.scope" "MemoryLimit=" "MemoryLimit=52428800"
-        else
-            check_systemd_value "runc-cgroups-integration-test.scope" "MemoryMax=" "MemoryMax=52428800"
-        fi
-    fi
+    check_cgroup_value $CGROUP_MEMORY "memory.limit_in_bytes" 52428800
 
     # update memory soft limit
     runc update test_update --memory-reservation 33554432
     [ "$status" -eq 0 ]
-    check_cgroup_value "$MEM_RESERVE" 33554432
+    check_cgroup_value $CGROUP_MEMORY "memory.soft_limit_in_bytes" 33554432
 
     # Run swap memory tests if swap is available
-    if [ -f "$CGROUP_MEMORY/$MEM_SWAP" ]; then
+    if [ -f "$CGROUP_MEMORY/memory.memsw.limit_in_bytes" ]; then
         # try to remove memory swap limit
         runc update test_update --memory-swap -1
         [ "$status" -eq 0 ]
-        check_cgroup_value "$MEM_SWAP" $SYSTEM_MEM_SWAP
+        # Get System memory swap limit
+        SYSTEM_MEMORY_SW=$(cat "${CGROUP_SYSTEM_MEMORY}/memory.memsw.limit_in_bytes")
+        check_cgroup_value $CGROUP_MEMORY "memory.memsw.limit_in_bytes" ${SYSTEM_MEMORY_SW}
 
         # update memory swap
         runc update test_update --memory-swap 96468992
         [ "$status" -eq 0 ]
-        check_cgroup_value "$MEM_SWAP" 96468992
-    fi
+        check_cgroup_value $CGROUP_MEMORY "memory.memsw.limit_in_bytes" 96468992
+    fi;
 
     # try to remove memory limit
     runc update test_update --memory -1
     [ "$status" -eq 0 ]
 
-    # check memory limit is gone
-    check_cgroup_value $MEM_LIMIT $SYSTEM_MEM
+    # Get System memory limit
+    SYSTEM_MEMORY=$(cat "${CGROUP_SYSTEM_MEMORY}/memory.limit_in_bytes")
+   	# check memory limited is gone
+    check_cgroup_value $CGROUP_MEMORY "memory.limit_in_bytes" ${SYSTEM_MEMORY}
 
     # check swap memory limited is gone
-    if [ -f "$CGROUP_MEMORY/$MEM_SWAP" ]; then
-        check_cgroup_value $MEM_SWAP $SYSTEM_MEM
+    if [ -f "$CGROUP_MEMORY/memory.memsw.limit_in_bytes" ]; then
+        check_cgroup_value $CGROUP_MEMORY "memory.memsw.limit_in_bytes" ${SYSTEM_MEMORY}
     fi
 
+    # update kernel memory limit
+    runc update test_update --kernel-memory 50331648
+    [ "$status" -eq 0 ]
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.limit_in_bytes" 50331648
+
+    # update kernel memory tcp limit
+    runc update test_update --kernel-memory-tcp 41943040
+    [ "$status" -eq 0 ]
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.tcp.limit_in_bytes" 41943040
+
     # update pids limit
     runc update test_update --pids-limit 10
     [ "$status" -eq 0 ]
-    check_cgroup_value "pids.max" 10
-    if [[ -n "${RUNC_USE_SYSTEMD}" ]] ; then
-        check_systemd_value "runc-cgroups-integration-test.scope" "TasksMax=" "TasksMax=10"
-    fi
+    check_cgroup_value $CGROUP_PIDS "pids.max" 10
 
-    # Revert to the test initial value via json on stdin
+    # Revert to the test initial value via json on stding
     runc update  -r - test_update <<EOF
 {
   "memory": {
     "limit": 33554432,
-    "reservation": 25165824
+    "reservation": 25165824,
+    "kernel": 16777216,
+    "kernelTCP": 11534336
   },
   "cpu": {
     "shares": 100,
@@ -160,27 +179,39 @@ EOF
 }
 EOF
     [ "$status" -eq 0 ]
-    check_cgroup_value "cpuset.cpus" 0
-    check_cgroup_value $MEM_LIMIT 33554432
-    check_cgroup_value $MEM_RESERVE 25165824
-    check_cgroup_value "pids.max" 20
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_period_us" 1000000
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_quota_us" 500000
+    check_cgroup_value $CGROUP_CPU "cpu.shares" 100
+    check_cgroup_value $CGROUP_CPUSET "cpuset.cpus" 0
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.limit_in_bytes" 16777216
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.tcp.limit_in_bytes" 11534336
+    check_cgroup_value $CGROUP_MEMORY "memory.limit_in_bytes" 33554432
+    check_cgroup_value $CGROUP_MEMORY "memory.soft_limit_in_bytes" 25165824
+    check_cgroup_value $CGROUP_PIDS "pids.max" 20
 
     # redo all the changes at once
     runc update test_update \
-        --cpu-period 900000 --cpu-quota 600000 --cpu-share 200 \
-        --memory 67108864 --memory-reservation 33554432 \
+        --cpu-period 900000 --cpu-quota 600000 --cpu-share 200 --memory 67108864 \
+        --memory-reservation 33554432 --kernel-memory 50331648 --kernel-memory-tcp 41943040 \
         --pids-limit 10
     [ "$status" -eq 0 ]
-    check_cgroup_value $MEM_LIMIT 67108864
-    check_cgroup_value $MEM_RESERVE 33554432
-    check_cgroup_value "pids.max" 10
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_period_us" 900000
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_quota_us" 600000
+    check_cgroup_value $CGROUP_CPU "cpu.shares" 200
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.limit_in_bytes" 50331648
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.tcp.limit_in_bytes" 41943040
+    check_cgroup_value $CGROUP_MEMORY "memory.limit_in_bytes" 67108864
+    check_cgroup_value $CGROUP_MEMORY "memory.soft_limit_in_bytes" 33554432
+    check_cgroup_value $CGROUP_PIDS "pids.max" 10
 
     # reset to initial test value via json file
-    cat << EOF > $BATS_TMPDIR/runc-cgroups-integration-test.json
+    DATA=$(cat <<"EOF"
 {
   "memory": {
     "limit": 33554432,
-    "reservation": 25165824
+    "reservation": 25165824,
+    "kernel": 16777216,
+    "kernelTCP": 11534336
   },
   "cpu": {
     "shares": 100,
@@ -193,92 +224,33 @@ EOF
   }
 }
 EOF
+)
+    echo $DATA > $BATS_TMPDIR/runc-cgroups-integration-test.json
 
     runc update  -r $BATS_TMPDIR/runc-cgroups-integration-test.json test_update
     [ "$status" -eq 0 ]
-    check_cgroup_value "cpuset.cpus" 0
-    check_cgroup_value $MEM_LIMIT 33554432
-    check_cgroup_value $MEM_RESERVE 25165824
-    check_cgroup_value "pids.max" 20
-}
-
-@test "update cgroup v1 cpu limits" {
-    [[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
-    requires cgroups_v1
-
-    # run a few busyboxes detached
-    runc run -d --console-socket $CONSOLE_SOCKET test_update
-    [ "$status" -eq 0 ]
-
-    # check that initial values were properly set
-    check_cgroup_value "cpu.cfs_period_us" 1000000
-    check_cgroup_value "cpu.cfs_quota_us" 500000
-    check_cgroup_value "cpu.shares" 100
-
-    # update cpu-period
-    runc update test_update --cpu-period 900000
-    [ "$status" -eq 0 ]
-    check_cgroup_value "cpu.cfs_period_us" 900000
-
-    # update cpu-quota
-    runc update test_update --cpu-quota 600000
-    [ "$status" -eq 0 ]
-    check_cgroup_value "cpu.cfs_quota_us" 600000
-
-    # update cpu-shares
-    runc update test_update --cpu-share 200
-    [ "$status" -eq 0 ]
-    check_cgroup_value "cpu.shares" 200
-
-    # Revert to the test initial value via json on stding
-    runc update  -r - test_update <<EOF
-{
-  "cpu": {
-    "shares": 100,
-    "quota": 500000,
-    "period": 1000000
-  }
-}
-EOF
-    [ "$status" -eq 0 ]
-    check_cgroup_value "cpu.cfs_period_us" 1000000
-    check_cgroup_value "cpu.cfs_quota_us" 500000
-    check_cgroup_value "cpu.shares" 100
-
-    # redo all the changes at once
-    runc update test_update \
-        --cpu-period 900000 --cpu-quota 600000 --cpu-share 200
-    [ "$status" -eq 0 ]
-    check_cgroup_value "cpu.cfs_period_us" 900000
-    check_cgroup_value "cpu.cfs_quota_us" 600000
-    check_cgroup_value "cpu.shares" 200
-
-    # reset to initial test value via json file
-    cat << EOF > $BATS_TMPDIR/runc-cgroups-integration-test.json
-{
-  "cpu": {
-    "shares": 100,
-    "quota": 500000,
-    "period": 1000000
-  }
-}
-EOF
-
-    runc update  -r $BATS_TMPDIR/runc-cgroups-integration-test.json test_update
-    [ "$status" -eq 0 ]
-    check_cgroup_value "cpu.cfs_period_us" 1000000
-    check_cgroup_value "cpu.cfs_quota_us" 500000
-    check_cgroup_value "cpu.shares" 100
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_period_us" 1000000
+    check_cgroup_value $CGROUP_CPU "cpu.cfs_quota_us" 500000
+    check_cgroup_value $CGROUP_CPU "cpu.shares" 100
+    check_cgroup_value $CGROUP_CPUSET "cpuset.cpus" 0
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.limit_in_bytes" 16777216
+    check_cgroup_value $CGROUP_MEMORY "memory.kmem.tcp.limit_in_bytes" 11534336
+    check_cgroup_value $CGROUP_MEMORY "memory.limit_in_bytes" 33554432
+    check_cgroup_value $CGROUP_MEMORY "memory.soft_limit_in_bytes" 25165824
+    check_cgroup_value $CGROUP_PIDS "pids.max" 20
 }
 
 @test "update rt period and runtime" {
     [[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
-    requires cgroups_rt
+    requires cgroups_kmem cgroups_rt
 
     # run a detached busybox
     runc run -d --console-socket $CONSOLE_SOCKET test_update_rt
     [ "$status" -eq 0 ]
 
+    # get the cgroup paths
+    eval CGROUP_CPU="${CGROUP_CPU_BASE_PATH}${CGROUPS_PATH}"
+
     runc update  -r - test_update_rt <<EOF
 {
   "cpu": {
@@ -287,11 +259,11 @@ EOF
   }
 }
 EOF
-    check_cgroup_value "cpu.rt_period_us" 800001
-    check_cgroup_value "cpu.rt_runtime_us" 500001
+    check_cgroup_value $CGROUP_CPU "cpu.rt_period_us" 800001
+    check_cgroup_value $CGROUP_CPU "cpu.rt_runtime_us" 500001
 
     runc update test_update_rt --cpu-rt-period 900001 --cpu-rt-runtime 600001
 
-    check_cgroup_value "cpu.rt_period_us" 900001
-    check_cgroup_value "cpu.rt_runtime_us" 600001
+    check_cgroup_value $CGROUP_CPU "cpu.rt_period_us" 900001
+    check_cgroup_value $CGROUP_CPU "cpu.rt_runtime_us" 600001
 }