Repository: opencontainers/runc
Pull request #2278 (2020-03-30 07:46:14)

Exposing memory.numa_stats
Making information on page usage by type and NUMA node available.

AkihiroSuda: Code looks good, but needs rebase
iwankgb: @AkihiroSuda rebased :)
AkihiroSuda: LGTM
iwankgb: @caniszczyk, @crosbymichael, @cyphar, @dqminh, @hqhq, @mrunalp, @rjnagal, @vmarmol - second opinion would be appreciated.

Patch:
diff --git a/.travis.yml b/.travis.yml
index 706a8cea..b762b632 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,9 +4,7 @@ go:
   - 1.14.x
   - 1.13.x
   - tip
-cache:
-  directories:
-  - /home/travis/.vagrant.d/boxes
+
 matrix:
   include:
     - go: 1.14.x
@@ -23,17 +21,18 @@ matrix:
     - go: 1.13.x
       name: "cgroup-v2"
       env:
-        - VAGRANT_VERSION=2.2.7
+        - VIRTUALBOX_VERSION=6.0
+        - VAGRANT_VERSION=2.2.6
+        - FEDORA_VERSION=31
       before_install:
         - cat /proc/cpuinfo
-# https://github.com/alvistack/ansible-role-virtualbox/blob/6887b020b0ca5c59ddb6620d73f053ffb84f4126/.travis.yml#L30
-        - sudo apt-get install -q -y bridge-utils dnsmasq-base ebtables libvirt-bin libvirt-dev qemu-kvm qemu-utils ruby-dev && wget https://releases.hashicorp.com/vagrant/${VAGRANT_VERSION}/vagrant_${VAGRANT_VERSION}_$(uname -m).deb && sudo dpkg -i vagrant_${VAGRANT_VERSION}_$(uname -m).deb && rm -f vagrant_${VAGRANT_VERSION}_$(uname -m).deb
-        - sudo vagrant plugin install vagrant-libvirt
-        - sudo vagrant up && sudo mkdir -p /root/.ssh && sudo sh -c "vagrant ssh-config >> /root/.ssh/config"
+        - wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add - && sudo sh -c "echo deb https://download.virtualbox.org/virtualbox/debian $(lsb_release -cs) contrib >> /etc/apt/sources.list" && sudo apt-get update && sudo apt-get install -yq build-essential gcc make linux-headers-$(uname -r) virtualbox-${VIRTUALBOX_VERSION} && sudo usermod -aG vboxusers $(whoami)
+        - wget https://releases.hashicorp.com/vagrant/${VAGRANT_VERSION}/vagrant_${VAGRANT_VERSION}_$(uname -m).deb && sudo dpkg -i vagrant_${VAGRANT_VERSION}_$(uname -m).deb
+        - vagrant init bento/fedora-${FEDORA_VERSION} && vagrant up && mkdir -p ~/.ssh && vagrant ssh-config >> ~/.ssh/config
+        - ssh default sudo dnf install -y podman
       script:
-        - 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
+        - ssh default sudo podman build -t test /vagrant
+        - ssh default sudo podman run --privileged --cgroupns=private test make localunittest
   allow_failures:
     - go: tip
     - name: "cgroup-v2"
diff --git a/Dockerfile b/Dockerfile
index 94be151e..d3e91bc7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
 ARG GO_VERSION=1.13
 ARG BATS_VERSION=03608115df2071fff4eaaff1605768c275e5f81f
-ARG CRIU_VERSION=v3.13
+ARG CRIU_VERSION=v3.12
 
 FROM golang:${GO_VERSION}-buster
 ARG DEBIAN_FRONTEND=noninteractive
@@ -62,13 +62,7 @@ ARG CRIU_VERSION
 RUN 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
 
 COPY script/tmpmount /
diff --git a/README.md b/README.md
index 7ca535df..765591ff 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ A third party security audit was performed by Cure53, you can see the full repor
 ## Building
 
 `runc` currently supports the Linux platform with various architecture support.
-It must be built with Go version 1.13 or higher.
+It must be built with Go version 1.6 or higher in order for some features to function properly.
 
 In order to enable seccomp support you will need to install `libseccomp` on your platform.
 > e.g. `libseccomp-devel` for CentOS, or `libseccomp-dev` for Ubuntu
diff --git a/Vagrantfile b/Vagrantfile
deleted file mode 100644
index 165b0780..00000000
--- a/Vagrantfile
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- mode: ruby -*-
-# vi: set ft=ruby :
-
-Vagrant.configure("2") do |config|
-# Fedora 31 box is used for testing cgroup v2 support
-  config.vm.box = "fedora/31-cloud-base"
-  config.vm.provider :virtualbox do |v|
-    v.memory = 2048
-    v.cpus = 2
-  end
-  config.vm.provider :libvirt do |v|
-    v.memory = 2048
-    v.cpus = 2
-  end
-  config.vm.provision "shell", inline: <<-SHELL
-    dnf install -y podman
-  SHELL
-end
diff --git a/libcontainer/cgroups/fs/apply_raw.go b/libcontainer/cgroups/fs/apply_raw.go
index df83d41c..ec148b48 100644
--- a/libcontainer/cgroups/fs/apply_raw.go
+++ b/libcontainer/cgroups/fs/apply_raw.go
@@ -8,7 +8,6 @@ import (
 	"os"
 	"path/filepath"
 	"sync"
-	"syscall"
 
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/configs"
@@ -111,18 +110,22 @@ func isIgnorableError(rootless bool, err error) bool {
 	if !rootless {
 		return false
 	}
-	// TODO: rm errors.Cause once we switch to %w everywhere
-	err = errors.Cause(err)
 	// Is it an ordinary EPERM?
-	if errors.Is(err, os.ErrPermission) {
+	if os.IsPermission(errors.Cause(err)) {
 		return true
 	}
-	// Handle some specific syscall errors.
-	var errno syscall.Errno
-	if errors.As(err, &errno) {
-		return errno == unix.EROFS || errno == unix.EPERM || errno == unix.EACCES
+
+	// Try to handle other errnos.
+	var errno error
+	switch err := errors.Cause(err).(type) {
+	case *os.PathError:
+		errno = err.Err
+	case *os.LinkError:
+		errno = err.Err
+	case *os.SyscallError:
+		errno = err.Err
 	}
-	return false
+	return errno == unix.EROFS || errno == unix.EPERM || errno == unix.EACCES
 }
 
 func (m *Manager) getSubsystems() subsystemSet {
diff --git a/libcontainer/cgroups/fs/kmem.go b/libcontainer/cgroups/fs/kmem.go
index 8d97a679..69b5a194 100644
--- a/libcontainer/cgroups/fs/kmem.go
+++ b/libcontainer/cgroups/fs/kmem.go
@@ -6,8 +6,10 @@ import (
 	"errors"
 	"fmt"
 	"io/ioutil"
+	"os"
 	"path/filepath"
 	"strconv"
+	"syscall" // for Errno type only
 
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"golang.org/x/sys/unix"
@@ -47,8 +49,12 @@ func setKernelMemory(path string, kernelMemoryLimit int64) error {
 		// The EBUSY signal is returned on attempts to write to the
 		// memory.kmem.limit_in_bytes file if the cgroup has children or
 		// once tasks have been attached to the cgroup
-		if errors.Is(err, unix.EBUSY) {
-			return fmt.Errorf("failed to set %s, because either tasks have already joined this cgroup or it has children", cgroupKernelMemoryLimit)
+		if pathErr, ok := err.(*os.PathError); ok {
+			if errNo, ok := pathErr.Err.(syscall.Errno); ok {
+				if errNo == unix.EBUSY {
+					return fmt.Errorf("failed to set %s, because either tasks have already joined this cgroup or it has children", cgroupKernelMemoryLimit)
+				}
+			}
 		}
 		return fmt.Errorf("failed to write %v to %v: %v", kernelMemoryLimit, cgroupKernelMemoryLimit, err)
 	}
diff --git a/libcontainer/cgroups/fs/memory.go b/libcontainer/cgroups/fs/memory.go
index f81ed050..39809ef0 100644
--- a/libcontainer/cgroups/fs/memory.go
+++ b/libcontainer/cgroups/fs/memory.go
@@ -5,7 +5,9 @@ package fs
 import (
 	"bufio"
 	"fmt"
+	"math"
 	"os"
+	"path"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -16,8 +18,16 @@ import (
 )
 
 const (
-	cgroupMemorySwapLimit = "memory.memsw.limit_in_bytes"
-	cgroupMemoryLimit     = "memory.limit_in_bytes"
+	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"
 )
 
 type MemoryGroup struct {
@@ -209,6 +219,13 @@ 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
 }
 
@@ -269,3 +286,77 @@ 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/fs2.go b/libcontainer/cgroups/fs2/fs2.go
index 4e0cb6fa..4bb7091a 100644
--- a/libcontainer/cgroups/fs2/fs2.go
+++ b/libcontainer/cgroups/fs2/fs2.go
@@ -93,49 +93,41 @@ func (m *manager) GetAllPids() ([]int, error) {
 
 func (m *manager) GetStats() (*cgroups.Stats, error) {
 	var (
+		st   cgroups.Stats
 		errs []error
 	)
-
-	st := cgroups.NewStats()
-
 	// pids (since kernel 4.5)
 	if _, ok := m.controllers["pids"]; ok {
-		if err := statPids(m.dirPath, st); err != nil {
+		if err := statPids(m.dirPath, &st); err != nil {
 			errs = append(errs, err)
 		}
 	} else {
-		if err := statPidsWithoutController(m.dirPath, st); err != nil {
+		if err := statPidsWithoutController(m.dirPath, &st); err != nil {
 			errs = append(errs, err)
 		}
 	}
-	// memory (since kernel 4.5)
+	// memory (since kenrel 4.5)
 	if _, ok := m.controllers["memory"]; ok {
-		if err := statMemory(m.dirPath, st); err != nil {
+		if err := statMemory(m.dirPath, &st); err != nil {
 			errs = append(errs, err)
 		}
 	}
 	// io (since kernel 4.5)
 	if _, ok := m.controllers["io"]; ok {
-		if err := statIo(m.dirPath, st); err != nil {
+		if err := statIo(m.dirPath, &st); err != nil {
 			errs = append(errs, err)
 		}
 	}
 	// cpu (since kernel 4.15)
 	if _, ok := m.controllers["cpu"]; ok {
-		if err := statCpu(m.dirPath, st); err != nil {
-			errs = append(errs, err)
-		}
-	}
-	// hugetlb (since kernel 5.6)
-	if _, ok := m.controllers["hugetlb"]; ok {
-		if err := statHugeTlb(m.dirPath, st); err != nil {
+		if err := statCpu(m.dirPath, &st); err != nil {
 			errs = append(errs, err)
 		}
 	}
 	if len(errs) > 0 && !m.rootless {
-		return st, errors.Errorf("error while statting cgroup v2: %+v", errs)
+		return &st, errors.Errorf("error while statting cgroup v2: %+v", errs)
 	}
-	return st, nil
+	return &st, nil
 }
 
 func (m *manager) Freeze(state configs.FreezerState) error {
@@ -206,12 +198,6 @@ func (m *manager) Set(container *configs.Config) error {
 			errs = append(errs, err)
 		}
 	}
-	// hugetlb (since kernel 5.6)
-	if _, ok := m.controllers["hugetlb"]; ok {
-		if err := setHugeTlb(m.dirPath, container.Cgroups); err != nil {
-			errs = append(errs, err)
-		}
-	}
 	// freezer (since kernel 5.2, pseudo-controller)
 	if err := setFreezer(m.dirPath, container.Cgroups.Freezer); err != nil {
 		errs = append(errs, err)
diff --git a/libcontainer/cgroups/fs2/hugetlb.go b/libcontainer/cgroups/fs2/hugetlb.go
deleted file mode 100644
index c7610c0a..00000000
--- a/libcontainer/cgroups/fs2/hugetlb.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// +build linux
-
-package fs2
-
-import (
-	"io/ioutil"
-	"path/filepath"
-	"strconv"
-	"strings"
-
-	"github.com/pkg/errors"
-
-	"github.com/opencontainers/runc/libcontainer/cgroups"
-	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
-	"github.com/opencontainers/runc/libcontainer/configs"
-)
-
-func setHugeTlb(dirPath string, cgroup *configs.Cgroup) error {
-	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
-		}
-	}
-
-	return nil
-}
-
-func statHugeTlb(dirPath string, stats *cgroups.Stats) error {
-	hugePageSizes, err := cgroups.GetHugePageSize()
-	if err != nil {
-		return errors.Wrap(err, "failed to fetch hugetlb info")
-	}
-	hugetlbStats := cgroups.HugetlbStats{}
-
-	for _, pagesize := range hugePageSizes {
-		usage := strings.Join([]string{"hugetlb", pagesize, "current"}, ".")
-		value, err := fscommon.GetCgroupParamUint(dirPath, usage)
-		if err != nil {
-			return errors.Wrapf(err, "failed to parse hugetlb.%s.current file", pagesize)
-		}
-		hugetlbStats.Usage = value
-
-		fileName := strings.Join([]string{"hugetlb", pagesize, "events"}, ".")
-		filePath := filepath.Join(dirPath, fileName)
-		contents, err := ioutil.ReadFile(filePath)
-		if err != nil {
-			return errors.Wrapf(err, "failed to parse hugetlb.%s.events file", pagesize)
-		}
-		_, value, err = fscommon.GetCgroupParamKeyValue(string(contents))
-		if err != nil {
-			return errors.Wrapf(err, "failed to parse hugetlb.%s.events file", pagesize)
-		}
-		hugetlbStats.Failcnt = value
-
-		stats.HugetlbStats[pagesize] = hugetlbStats
-	}
-
-	return nil
-}
diff --git a/libcontainer/cgroups/fs2/memory.go b/libcontainer/cgroups/fs2/memory.go
index 00415ecd..f1db0786 100644
--- a/libcontainer/cgroups/fs2/memory.go
+++ b/libcontainer/cgroups/fs2/memory.go
@@ -17,16 +17,15 @@ import (
 
 // numToStr converts an int64 value to a string for writing to a
 // cgroupv2 files with .min, .max, .low, or .high suffix.
-// The value of -1 is converted to "max" for cgroupv1 compatibility
+// Negative values are converted to "max" for cgroupv1 compatibility
 // (which used to write -1 to remove the limit).
 func numToStr(value int64) (ret string) {
-	switch {
-	case value == 0:
-		ret = ""
-	case value == -1:
-		ret = "max"
-	default:
+	if value > 0 {
 		ret = strconv.FormatInt(value, 10)
+	} else if value < 0 {
+		ret = "max"
+	} else {
+		ret = ""
 	}
 
 	return ret
diff --git a/libcontainer/cgroups/fs2/pids.go b/libcontainer/cgroups/fs2/pids.go
index 0cf860f9..99d912cf 100644
--- a/libcontainer/cgroups/fs2/pids.go
+++ b/libcontainer/cgroups/fs2/pids.go
@@ -4,6 +4,7 @@ package fs2
 
 import (
 	"io/ioutil"
+	"os"
 	"path/filepath"
 	"strings"
 
@@ -24,11 +25,20 @@ func setPids(dirPath string, cgroup *configs.Cgroup) error {
 	return nil
 }
 
+func isNOTSUP(err error) bool {
+	switch err := err.(type) {
+	case *os.PathError:
+		return err.Err == unix.ENOTSUP
+	default:
+		return false
+	}
+}
+
 func statPidsWithoutController(dirPath string, stats *cgroups.Stats) error {
 	// if the controller is not enabled, let's read PIDS from cgroups.procs
 	// (or threads if cgroup.threads is enabled)
 	contents, err := ioutil.ReadFile(filepath.Join(dirPath, "cgroup.procs"))
-	if errors.Is(err, unix.ENOTSUP) {
+	if err != nil && isNOTSUP(err) {
 		contents, err = ioutil.ReadFile(filepath.Join(dirPath, "cgroup.threads"))
 	}
 	if err != nil {
diff --git a/libcontainer/cgroups/stats.go b/libcontainer/cgroups/stats.go
index 8eeedc55..eaf8f234 100644
--- a/libcontainer/cgroups/stats.go
+++ b/libcontainer/cgroups/stats.go
@@ -51,12 +51,33 @@ 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/apply_nosystemd.go b/libcontainer/cgroups/systemd/apply_nosystemd.go
index 5318a1e9..ef0db5ae 100644
--- a/libcontainer/cgroups/systemd/apply_nosystemd.go
+++ b/libcontainer/cgroups/systemd/apply_nosystemd.go
@@ -14,7 +14,7 @@ type Manager struct {
 	Paths   map[string]string
 }
 
-func IsRunningSystemd() bool {
+func UseSystemd() bool {
 	return false
 }
 
diff --git a/libcontainer/cgroups/systemd/apply_systemd.go b/libcontainer/cgroups/systemd/apply_systemd.go
index 4878448d..cd83fadd 100644
--- a/libcontainer/cgroups/systemd/apply_systemd.go
+++ b/libcontainer/cgroups/systemd/apply_systemd.go
@@ -65,10 +65,14 @@ var legacySubsystems = subsystemSet{
 	&fs.NameGroup{GroupName: "name=systemd"},
 }
 
+const (
+	testScopeWait = 4
+	testSliceWait = 4
+)
+
 var (
-	connOnce sync.Once
-	connDbus *systemdDbus.Conn
-	connErr  error
+	connLock sync.Mutex
+	theConn  *systemdDbus.Conn
 )
 
 func newProp(name string, units interface{}) systemdDbus.Property {
@@ -85,7 +89,7 @@ func newProp(name string, units interface{}) systemdDbus.Property {
 // 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 {
+func isRunningSystemd() bool {
 	fi, err := os.Lstat("/run/systemd/system")
 	if err != nil {
 		return false
@@ -93,17 +97,26 @@ func IsRunningSystemd() bool {
 	return fi.IsDir()
 }
 
-// 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 UseSystemd() bool {
+	if !isRunningSystemd() {
+		return false
+	}
+
+	connLock.Lock()
+	defer connLock.Unlock()
+
+	if theConn == nil {
+		var err error
+		theConn, err = systemdDbus.New()
+		if err != nil {
+			return false
+		}
+	}
+	return true
 }
 
 func NewSystemdCgroupsManager() (func(config *configs.Cgroup, paths map[string]string) cgroups.Manager, error) {
-	if !IsRunningSystemd() {
+	if !isRunningSystemd() {
 		return nil, fmt.Errorf("systemd not running on this host, can't use systemd as a cgroups.Manager")
 	}
 	if cgroups.IsCgroup2UnifiedMode() {
@@ -234,12 +247,8 @@ func (m *LegacyManager) Apply(pid int) error {
 
 	properties = append(properties, c.SystemdProps...)
 
-	dbusConnection, err := getDbusConnection()
-	if err != nil {
-		return err
-	}
 	statusChan := make(chan string, 1)
-	if _, err := dbusConnection.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
+	if _, err := theConn.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
 		select {
 		case <-statusChan:
 		case <-time.After(time.Second):
@@ -275,13 +284,7 @@ func (m *LegacyManager) Destroy() error {
 	}
 	m.mu.Lock()
 	defer m.mu.Unlock()
-
-	dbusConnection, err := getDbusConnection()
-	if err != nil {
-		return err
-	}
-
-	dbusConnection.StopUnit(getUnitName(m.Cgroups), "replace", nil)
+	theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil)
 	if err := cgroups.RemovePaths(m.Paths); err != nil {
 		return err
 	}
diff --git a/libcontainer/cgroups/systemd/unified_hierarchy.go b/libcontainer/cgroups/systemd/unified_hierarchy.go
index 7fab68fc..308a7917 100644
--- a/libcontainer/cgroups/systemd/unified_hierarchy.go
+++ b/libcontainer/cgroups/systemd/unified_hierarchy.go
@@ -119,13 +119,8 @@ func (m *UnifiedManager) Apply(pid int) error {
 
 	// ignore c.Resources.KernelMemory
 
-	dbusConnection, err := getDbusConnection()
-	if err != nil {
-		return err
-	}
-
 	statusChan := make(chan string, 1)
-	if _, err := dbusConnection.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
+	if _, err := theConn.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
 		select {
 		case <-statusChan:
 		case <-time.After(time.Second):
@@ -160,12 +155,7 @@ func (m *UnifiedManager) Destroy() error {
 	}
 	m.mu.Lock()
 	defer m.mu.Unlock()
-
-	dbusConnection, err := getDbusConnection()
-	if err != nil {
-		return err
-	}
-	dbusConnection.StopUnit(getUnitName(m.Cgroups), "replace", nil)
+	theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil)
 	if err := cgroups.RemovePaths(m.Paths); err != nil {
 		return err
 	}
diff --git a/libcontainer/cgroups/utils.go b/libcontainer/cgroups/utils.go
index 51b743e2..7808c093 100644
--- a/libcontainer/cgroups/utils.go
+++ b/libcontainer/cgroups/utils.go
@@ -576,7 +576,7 @@ func WriteCgroupProc(dir string, pid int) error {
 
 		// EINVAL might mean that the task being added to cgroup.procs is in state
 		// TASK_NEW. We should attempt to do so again.
-		if errors.Is(err, unix.EINVAL) {
+		if isEINVAL(err) {
 			time.Sleep(30 * time.Millisecond)
 			continue
 		}
@@ -586,6 +586,15 @@ func WriteCgroupProc(dir string, pid int) error {
 	return err
 }
 
+func isEINVAL(err error) bool {
+	switch err := err.(type) {
+	case *os.PathError:
+		return err.Err == unix.EINVAL
+	default:
+		return false
+	}
+}
+
 // Since the OCI spec is designed for cgroup v1, in some cases
 // there is need to convert from the cgroup v1 configuration to cgroup v2
 // the formula for BlkIOWeight is y = (1 + (x - 10) * 9999 / 990)
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index 40797434..b7c9173e 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -1015,14 +1015,9 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
 		}
 	}
 
-	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)
-		}
+	fcg := c.cgroupManager.GetPaths()["freezer"]
+	if fcg != "" {
+		rpcOpts.FreezeCgroup = proto.String(fcg)
 	}
 
 	// append optional criu opts, e.g., page-server and port
@@ -1093,11 +1088,6 @@ func (c *linuxContainer) Checkpoint(criuOpts *CriuOpts) error {
 			case "bind":
 				c.addCriuDumpMount(req, m)
 			case "cgroup":
-				if cgroups.IsCgroup2UnifiedMode() {
-					c.addCriuDumpMount(req, m)
-					continue
-				}
-				// cgroup v1
 				binds, err := getCgroupMounts(m)
 				if err != nil {
 					return err
@@ -1204,10 +1194,10 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
 func isPathInPrefixList(path string, prefix []string) bool {
 	for _, p := range prefix {
 		if strings.HasPrefix(path, p+"/") {
-			return true
+			return false
 		}
 	}
-	return false
+	return true
 }
 
 // prepareCriuRestoreMounts tries to set up the rootfs of the
@@ -1229,7 +1219,7 @@ func (c *linuxContainer) prepareCriuRestoreMounts(mounts []*configs.Mount) error
 	// if the mountpoints are not on a tmpfs, as CRIU will
 	// restore the complete tmpfs content from its checkpoint.
 	for _, m := range mounts {
-		if !isPathInPrefixList(m.Destination, tmpfs) {
+		if isPathInPrefixList(m.Destination, tmpfs) {
 			if err := c.makeCriuRestoreMountpoints(m); err != nil {
 				return err
 			}
@@ -1362,11 +1352,6 @@ func (c *linuxContainer) Restore(process *Process, criuOpts *CriuOpts) error {
 		case "bind":
 			c.addCriuRestoreMount(req, m)
 		case "cgroup":
-			if cgroups.IsCgroup2UnifiedMode() {
-				c.addCriuRestoreMount(req, m)
-				continue
-			}
-			// cgroup v1
 			binds, err := getCgroupMounts(m)
 			if err != nil {
 				return err
@@ -1498,7 +1483,6 @@ func (c *linuxContainer) criuSwrk(process *Process, req *criurpc.CriuReq, opts *
 	if err := cmd.Start(); err != nil {
 		return err
 	}
-	// we close criuServer so that even if CRIU crashes or unexpectedly exits, runc will not hang.
 	criuServer.Close()
 	// cmd.Process will be replaced by a restored init.
 	criuProcess := cmd.Process
@@ -1833,23 +1817,14 @@ func (c *linuxContainer) runType() Status {
 }
 
 func (c *linuxContainer) isPaused() (bool, error) {
-	var fcg, filename, pausedState string
-
-	if !cgroups.IsCgroup2UnifiedMode() {
-		fcg = c.cgroupManager.GetPaths()["freezer"]
-		if fcg == "" {
-			// A container doesn't have a freezer cgroup
-			return false, nil
-		}
-		filename = "freezer.state"
-		pausedState = "FROZEN"
-	} else {
-		var err error
-		fcg, err = c.cgroupManager.GetUnifiedPath()
-		if err != nil {
-			// should not happen
-			return false, err
-		}
+	fcg := c.cgroupManager.GetPaths()["freezer"]
+	if fcg == "" {
+		// A container doesn't have a freezer cgroup
+		return false, nil
+	}
+	pausedState := "FROZEN"
+	filename := "freezer.state"
+	if cgroups.IsCgroup2UnifiedMode() {
 		filename = "cgroup.freeze"
 		pausedState = "1"
 	}
@@ -1857,7 +1832,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, syscall.ENODEV) {
+		if os.IsNotExist(err) || err == syscall.ENODEV {
 			return false, nil
 		}
 		return false, newSystemErrorWithCause(err, "checking if container is paused")
diff --git a/utils_linux.go b/utils_linux.go
index e4ba344c..1dedc827 100644
--- a/utils_linux.go
+++ b/utils_linux.go
@@ -47,7 +47,7 @@ func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
 		cgroupManager = libcontainer.RootlessCgroupfs
 	}
 	if context.GlobalBool("systemd-cgroup") {
-		if systemd.IsRunningSystemd() {
+		if systemd.UseSystemd() {
 			cgroupManager = libcontainer.SystemdCgroups
 		} else {
 			return nil, fmt.Errorf("systemd cgroup flag passed, but systemd support for managing cgroups is not available")


Test patch:
diff --git a/libcontainer/cgroups/fs/memory_test.go b/libcontainer/cgroups/fs/memory_test.go
index 62de5637..c84b4fe3 100644
--- a/libcontainer/cgroups/fs/memory_test.go
+++ b/libcontainer/cgroups/fs/memory_test.go
@@ -18,6 +18,18 @@ 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) {
@@ -276,6 +288,7 @@ 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{}
@@ -284,7 +297,21 @@ 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}
+	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}},
+			},
+		}}
 	expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats)
 }
 
@@ -454,3 +481,26 @@ 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 c5a8d185..cdce03cf 100644
--- a/libcontainer/cgroups/fs/stats_util_test.go
+++ b/libcontainer/cgroups/fs/stats_util_test.go
@@ -4,11 +4,10 @@ package fs
 
 import (
 	"fmt"
+	"reflect"
 	"testing"
 
 	"github.com/opencontainers/runc/libcontainer/cgroups"
-
-	"github.com/sirupsen/logrus"
 )
 
 func blkioStatEntryEquals(expected, actual []cgroups.BlkioStatEntry) error {
@@ -26,57 +25,47 @@ 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 {
-		logrus.Printf("blkio IoServiceBytesRecursive do not match - %s\n", err)
-		t.Fail()
+		t.Errorf("blkio IoServiceBytesRecursive do not match - %s\n", err)
 	}
 
 	if err := blkioStatEntryEquals(expected.IoServicedRecursive, actual.IoServicedRecursive); err != nil {
-		logrus.Printf("blkio IoServicedRecursive do not match - %s\n", err)
-		t.Fail()
+		t.Errorf("blkio IoServicedRecursive do not match - %s\n", err)
 	}
 
 	if err := blkioStatEntryEquals(expected.IoQueuedRecursive, actual.IoQueuedRecursive); err != nil {
-		logrus.Printf("blkio IoQueuedRecursive do not match - %s\n", err)
-		t.Fail()
+		t.Errorf("blkio IoQueuedRecursive do not match - %s\n", err)
 	}
 
 	if err := blkioStatEntryEquals(expected.SectorsRecursive, actual.SectorsRecursive); err != nil {
-		logrus.Printf("blkio SectorsRecursive do not match - %s\n", err)
-		t.Fail()
+		t.Errorf("blkio SectorsRecursive do not match - %s\n", err)
 	}
 
 	if err := blkioStatEntryEquals(expected.IoServiceTimeRecursive, actual.IoServiceTimeRecursive); err != nil {
-		logrus.Printf("blkio IoServiceTimeRecursive do not match - %s\n", err)
-		t.Fail()
+		t.Errorf("blkio IoServiceTimeRecursive do not match - %s\n", err)
 	}
 
 	if err := blkioStatEntryEquals(expected.IoWaitTimeRecursive, actual.IoWaitTimeRecursive); err != nil {
-		logrus.Printf("blkio IoWaitTimeRecursive do not match - %s\n", err)
-		t.Fail()
+		t.Errorf("blkio IoWaitTimeRecursive do not match - %s\n", err)
 	}
 
 	if err := blkioStatEntryEquals(expected.IoMergedRecursive, actual.IoMergedRecursive); err != nil {
-		logrus.Printf("blkio IoMergedRecursive do not match - %v vs %v\n", expected.IoMergedRecursive, actual.IoMergedRecursive)
-		t.Fail()
+		t.Errorf("blkio IoMergedRecursive do not match - %v vs %v\n", expected.IoMergedRecursive, actual.IoMergedRecursive)
 	}
 
 	if err := blkioStatEntryEquals(expected.IoTimeRecursive, actual.IoTimeRecursive); err != nil {
-		logrus.Printf("blkio IoTimeRecursive do not match - %s\n", err)
-		t.Fail()
+		t.Errorf("blkio IoTimeRecursive do not match - %s\n", err)
 	}
 }
 
 func expectThrottlingDataEquals(t *testing.T, expected, actual cgroups.ThrottlingData) {
 	if expected != actual {
-		logrus.Printf("Expected throttling data %v but found %v\n", expected, actual)
-		t.Fail()
+		t.Errorf("Expected throttling data %v but found %v\n", expected, actual)
 	}
 }
 
 func expectHugetlbStatEquals(t *testing.T, expected, actual cgroups.HugetlbStats) {
 	if expected != actual {
-		logrus.Printf("Expected hugetlb stats %v but found %v\n", expected, actual)
-		t.Fail()
+		t.Errorf("Expected hugetlb stats %v but found %v\n", expected, actual)
 	}
 }
 
@@ -84,40 +73,61 @@ 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 {
-		logrus.Printf("Expected memory use hierarchy %v, but found %v\n", expected.UseHierarchy, actual.UseHierarchy)
-		t.Fail()
+		t.Errorf("Expected memory use hierarchy %v, but found %v\n", expected.UseHierarchy, actual.UseHierarchy)
 	}
 
 	for key, expValue := range expected.Stats {
 		actValue, ok := actual.Stats[key]
 		if !ok {
-			logrus.Printf("Expected memory stat key %s not found\n", key)
-			t.Fail()
+			t.Errorf("Expected memory stat key %s not found\n", key)
 		}
 		if expValue != actValue {
-			logrus.Printf("Expected memory stat value %d but found %d\n", expValue, actValue)
-			t.Fail()
+			t.Errorf("Expected memory stat value %d but found %d\n", expValue, actValue)
 		}
 	}
 }
 
 func expectMemoryDataEquals(t *testing.T, expected, actual cgroups.MemoryData) {
 	if expected.Usage != actual.Usage {
-		logrus.Printf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage)
-		t.Fail()
+		t.Errorf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage)
 	}
 	if expected.MaxUsage != actual.MaxUsage {
-		logrus.Printf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage)
-		t.Fail()
+		t.Errorf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage)
 	}
 	if expected.Failcnt != actual.Failcnt {
-		logrus.Printf("Expected memory failcnt %d but found %d\n", expected.Failcnt, actual.Failcnt)
-		t.Fail()
+		t.Errorf("Expected memory failcnt %d but found %d\n", expected.Failcnt, actual.Failcnt)
 	}
 	if expected.Limit != actual.Limit {
-		logrus.Printf("Expected memory limit %d but found %d\n", expected.Limit, actual.Limit)
-		t.Fail()
+		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)
 	}
 }
diff --git a/libcontainer/container_linux_test.go b/libcontainer/container_linux_test.go
index 583c1c0c..f8af05d7 100644
--- a/libcontainer/container_linux_test.go
+++ b/libcontainer/container_linux_test.go
@@ -15,11 +15,10 @@ import (
 )
 
 type mockCgroupManager struct {
-	pids        []int
-	allPids     []int
-	stats       *cgroups.Stats
-	paths       map[string]string
-	unifiedPath string
+	pids    []int
+	allPids []int
+	stats   *cgroups.Stats
+	paths   map[string]string
 }
 
 type mockIntelRdtManager struct {
@@ -56,7 +55,7 @@ func (m *mockCgroupManager) GetPaths() map[string]string {
 }
 
 func (m *mockCgroupManager) GetUnifiedPath() (string, error) {
-	return m.unifiedPath, nil
+	return "", fmt.Errorf("unimplemented")
 }
 
 func (m *mockCgroupManager) Freeze(state configs.FreezerState) error {
diff --git a/libcontainer/integration/checkpoint_test.go b/libcontainer/integration/checkpoint_test.go
index 2f6990f8..51ed401f 100644
--- a/libcontainer/integration/checkpoint_test.go
+++ b/libcontainer/integration/checkpoint_test.go
@@ -60,6 +60,9 @@ func testCheckpoint(t *testing.T, userns bool) {
 	if testing.Short() {
 		return
 	}
+	if cgroups.IsCgroup2UnifiedMode() {
+		t.Skip("cgroup v2 is not supported")
+	}
 
 	root, err := newTestRoot()
 	if err != nil {
@@ -75,24 +78,16 @@ func testCheckpoint(t *testing.T, userns bool) {
 
 	config := newTemplateConfig(rootfs)
 
+	config.Mounts = append(config.Mounts, &configs.Mount{
+		Destination: "/sys/fs/cgroup",
+		Device:      "cgroup",
+		Flags:       defaultMountFlags | unix.MS_RDONLY,
+	})
+
 	if userns {
 		config.UidMappings = []configs.IDMap{{HostID: 0, ContainerID: 0, Size: 1000}}
 		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:      cgroupDevice,
-			Flags:       defaultMountFlags | unix.MS_RDONLY,
-		})
 	}
 
 	factory, err := libcontainer.New(root, libcontainer.Cgroupfs)
diff --git a/libcontainer/integration/exec_test.go b/libcontainer/integration/exec_test.go
index 3808099a..fa28e2db 100644
--- a/libcontainer/integration/exec_test.go
+++ b/libcontainer/integration/exec_test.go
@@ -506,7 +506,7 @@ func TestFreeze(t *testing.T) {
 }
 
 func TestSystemdFreeze(t *testing.T) {
-	if !systemd.IsRunningSystemd() {
+	if !systemd.UseSystemd() {
 		t.Skip("Systemd is unsupported")
 	}
 	testFreeze(t, true)
@@ -563,7 +563,7 @@ func TestCpuShares(t *testing.T) {
 }
 
 func TestCpuSharesSystemd(t *testing.T) {
-	if !systemd.IsRunningSystemd() {
+	if !systemd.UseSystemd() {
 		t.Skip("Systemd is unsupported")
 	}
 	testCpuShares(t, true)
@@ -598,7 +598,7 @@ func TestPids(t *testing.T) {
 }
 
 func TestPidsSystemd(t *testing.T) {
-	if !systemd.IsRunningSystemd() {
+	if !systemd.UseSystemd() {
 		t.Skip("Systemd is unsupported")
 	}
 	testPids(t, true)
@@ -684,7 +684,7 @@ func TestRunWithKernelMemory(t *testing.T) {
 }
 
 func TestRunWithKernelMemorySystemd(t *testing.T) {
-	if !systemd.IsRunningSystemd() {
+	if !systemd.UseSystemd() {
 		t.Skip("Systemd is unsupported")
 	}
 	testRunWithKernelMemory(t, true)