Repository: opencontainers/runc
Pull request #3157 (2021-08-12 18:17:43)

Logging improvements
This makes `runc --debug` logs more readable, along with some refactoring and logs tests improvements (originally part of #3114).

- init.go, main.go: don't use logs.ConfigureLogging
- libct/logs: remove ConfigureLogging
- Improve libct/logs and its tests
- libct/logs: test: make more robust
- libct/logs: parse log level implicitly
- libct/logs: do not show caller in nsexec logs
- runc --debug: shorter caller info

The biggest user-facing change is how debug logs are more clean / less cluttered:

```console
$ runc --debug run x
ERRO[0000]github.com/opencontainers/runc/utils.go:61 main.fatalWithCode() JSON specification file config.json not found

$ runc --debug run -d x
DEBU[0000]github.com/opencontainers/runc/libcontainer/logs/logs.go:69 github.com/opencontainers/runc/libcontainer/logs.processEntry() nsexec[2360844]: => nsexec container setup   
DEBU[0000]github.com/opencontainers/runc/libcontainer/logs/logs.go:69 github.com/opencontainers/runc/libcontainer/logs.processEntry() nsexec[2360844]: update /proc/self/oom_score_adj to '30' 
DEBU[0000]github.com/opencontainers/runc/libcontainer/logs/logs.go:69 github.com/opencontainers/runc/libcontainer/logs.processEntry() nsexec-0[2360844]: ~> nsexec stage-0         
DEBU[0000]github.com/opencontainers/runc/libcontainer/logs/logs.go:69 github.com/opencontainers/runc/libcontainer/logs.processEntry() nsexec-0[2360844]: spawn stage-1             
DEBU[0000]github.com/opencontainers/runc/libcontainer/logs/logs.go:69 github.com/opencontainers/runc/libcontainer/logs.processEntry() nsexec-0[2360844]: -> stage-1 synchronisation loop 
DEBU[0000]github.com/opencontainers/runc/libcontainer/logs/logs.go:69 github.com/opencontainers/runc/libcontainer/logs.processEntry() nsexec-1[2360846]: ~> nsexec stage-1         
DEBU[0000]github.com/opencontainers/runc/libcontainer/logs/logs.go:69 github.com/opencontainers/runc/libcontainer/logs.processEntry() nsexec-1[2360846]: unshare remaining namespace (except cgroupns) 
DEBU[0000]github.com/opencontainers/runc/libcontainer/logs/logs.go:69 github.com/opencontainers/runc/libcontainer/logs.processEntry() nsexec-1[2360846]: spawn stage-2             
DEBU[0000]github.com/opencontainers/runc/libcontainer/logs/logs.go:69 github.com/opencontainers/runc/libcontainer/logs.processEntry() nsexec-1[2360846]: request stage-0 to forward stage-2 pid (2360848) 
```

After:
```console
$ runc --debug run x
ERRO[0000]utils.go:61 main.fatalWithCode() JSON specification file config.json not found

$ runc --debug run -d x
DEBU[0000] nsexec[2359952]: => nsexec container setup   
DEBU[0000] nsexec[2359952]: update /proc/self/oom_score_adj to '30' 
DEBU[0000] nsexec-0[2359952]: ~> nsexec stage-0         
DEBU[0000] nsexec-0[2359952]: spawn stage-1             
DEBU[0000] nsexec-0[2359952]: -> stage-1 synchronisation loop 
DEBU[0000] nsexec-1[2359954]: ~> nsexec stage-1         
DEBU[0000] nsexec-1[2359954]: unshare remaining namespace (except cgroupns) 
DEBU[0000] nsexec-1[2359954]: spawn stage-2             
DEBU[0000] nsexec-1[2359954]: request stage-0 to forward stage-2 pid (2359955) 
...
```

Fixes: #3183
Fixes: #3184

kolyshkin: Thanks for review @thaJeztah, I implemented most of the suggested changes are explained the one that I did not ("the case of missing curly braces"). Also rebased and fixed a couple of commit message typos. PTAL
thaJeztah: We should consider if Logrus is still the best solution for us. It provides many features, somewhat nearing "feature-creep". It's also in "maintenance mode" mostly. I know there's more lightweight solutions (if performance matters), but haven't looked closely at them to see if they're useful.

Patch:
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 78bfe071..8ad6be73 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -25,9 +25,6 @@ jobs:
 
   compile-buildtags:
     runs-on: ubuntu-20.04
-    env:
-      # Don't ignore C warnings. Note that the output of "go env CGO_CFLAGS" by default is "-g -O2", so we keep them.
-      CGO_CFLAGS: -g -O2 -Werror
     steps:
       - uses: actions/checkout@v2
       - name: compile with no build tags
diff --git a/.gitignore b/.gitignore
index 98775d60..84485cb9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,6 @@ vendor/pkg
 /runc
 /runc-*
 contrib/cmd/recvtty/recvtty
-contrib/cmd/sd-helper/sd-helper
 man/man8
 release
 Vagrantfile
diff --git a/Makefile b/Makefile
index f83c3ec8..d386048d 100644
--- a/Makefile
+++ b/Makefile
@@ -30,15 +30,14 @@ GO_BUILD_STATIC := CGO_ENABLED=1 $(GO) build -trimpath $(EXTRA_FLAGS) -tags "$(B
 runc:
 	$(GO_BUILD) -o runc .
 
-all: runc recvtty sd-helper
+all: runc recvtty
 
-recvtty sd-helper:
-	$(GO_BUILD) -o contrib/cmd/$@/$@ ./contrib/cmd/$@
+recvtty:
+	$(GO_BUILD) -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
 
 static:
 	$(GO_BUILD_STATIC) -o runc .
 	$(GO_BUILD_STATIC) -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
-	$(GO_BUILD_STATIC) -o contrib/cmd/sd-helper/sd-helper ./contrib/cmd/sd-helper
 
 release:
 	script/release.sh -r release/$(VERSION) -v $(VERSION)
@@ -111,7 +110,6 @@ install-man: man
 clean:
 	rm -f runc runc-*
 	rm -f contrib/cmd/recvtty/recvtty
-	rm -f contrib/cmd/sd-helper/sd-helper
 	rm -rf release
 	rm -rf man/man8
 
@@ -120,7 +118,7 @@ cfmt:
 	indent -linux -l120 -il0 -ppi2 -cp1 -T size_t -T jmp_buf $(C_SRC)
 
 shellcheck:
-	shellcheck tests/integration/*.bats tests/integration/*.sh tests/integration/*.bash tests/*.sh script/release.sh
+	shellcheck tests/integration/*.bats tests/integration/*.sh tests/*.sh script/release.sh
 	# TODO: add shellcheck for more sh files
 
 shfmt:
@@ -149,7 +147,7 @@ localcross:
 	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 .
 
-.PHONY: runc all recvtty sd-helper static release dbuild lint man runcimage \
+.PHONY: runc all recvtty static release dbuild lint man runcimage \
 	test localtest unittest localunittest integration localintegration \
 	rootlessintegration localrootlessintegration shell install install-bash \
 	install-man clean cfmt shfmt shellcheck \
diff --git a/README.md b/README.md
index d7797653..5c754116 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
 
 ## Introduction
 
-`runc` is a CLI tool for spawning and running containers on Linux according to the OCI specification.
+`runc` is a CLI tool for spawning and running containers according to the OCI specification.
 
 ## Releases
 
@@ -23,7 +23,8 @@ A third party security audit was performed by Cure53, you can see the full repor
 
 ## Building
 
-`runc` only supports Linux. It must be built with Go version 1.15 or higher.
+`runc` currently supports the Linux platform with various architecture support.
+It must be built with Go version 1.15 or higher.
 
 In order to enable seccomp support you will need to install `libseccomp` on your platform.
 > e.g. `libseccomp-devel` for CentOS, or `libseccomp-dev` for Ubuntu
diff --git a/checkpoint.go b/checkpoint.go
index 699284f6..bf773a9b 100644
--- a/checkpoint.go
+++ b/checkpoint.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/contrib/cmd/sd-helper/helper.go b/contrib/cmd/sd-helper/helper.go
deleted file mode 100644
index 15612524..00000000
--- a/contrib/cmd/sd-helper/helper.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package main
-
-import (
-	"flag"
-	"fmt"
-	"os"
-
-	"github.com/sirupsen/logrus"
-
-	"github.com/opencontainers/runc/libcontainer/cgroups"
-	"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
-	"github.com/opencontainers/runc/libcontainer/configs"
-)
-
-func usage() {
-	fmt.Print(`Open Container Initiative contrib/cmd/sd-helper
-
-sd-helper is a tool that uses runc/libcontainer/cgroups/systemd package
-functionality to communicate to systemd in order to perform various operations.
-Currently this is limited to starting and stopping systemd transient slice
-units.
-
-Usage:
-	sd-helper [-debug] [-parent <pname>] {start|stop} <name>
-
-Example:
-	sd-helper -parent system.slice start system-pod123.slice
-`)
-	os.Exit(1)
-}
-
-var (
-	debug  = flag.Bool("debug", false, "enable debug output")
-	parent = flag.String("parent", "", "parent unit name")
-)
-
-func main() {
-	if !systemd.IsRunningSystemd() {
-		logrus.Fatal("systemd is required")
-	}
-
-	// Set the flags.
-	flag.Parse()
-	if *debug {
-		logrus.SetLevel(logrus.DebugLevel)
-	}
-	if flag.NArg() != 2 {
-		usage()
-	}
-
-	cmd := flag.Arg(0)
-	unit := flag.Arg(1)
-
-	err := unitCommand(cmd, unit, *parent)
-	if err != nil {
-		logrus.Fatal(err)
-	}
-}
-
-func newManager(config *configs.Cgroup) cgroups.Manager {
-	if cgroups.IsCgroup2UnifiedMode() {
-		return systemd.NewUnifiedManager(config, "", false)
-	}
-	return systemd.NewLegacyManager(config, nil)
-}
-
-func unitCommand(cmd, name, parent string) error {
-	podConfig := &configs.Cgroup{
-		Name:      name,
-		Parent:    parent,
-		Resources: &configs.Resources{},
-	}
-	pm := newManager(podConfig)
-
-	switch cmd {
-	case "start":
-		return pm.Apply(-1)
-	case "stop":
-		return pm.Destroy()
-	}
-
-	return fmt.Errorf("unknown command: %s", cmd)
-}
diff --git a/delete.go b/delete.go
index 6e6b463e..fc0c73f9 100644
--- a/delete.go
+++ b/delete.go
@@ -1,3 +1,5 @@
+// +build !solaris
+
 package main
 
 import (
diff --git a/events.go b/events.go
index 6cdc01cd..0d151066 100644
--- a/events.go
+++ b/events.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/exec.go b/exec.go
index 3649bdc1..f8baff3d 100644
--- a/exec.go
+++ b/exec.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/init.go b/init.go
index d3949295..61026e22 100644
--- a/init.go
+++ b/init.go
@@ -1,13 +1,11 @@
 package main
 
 import (
-	"fmt"
 	"os"
 	"runtime"
 	"strconv"
 
 	"github.com/opencontainers/runc/libcontainer"
-	"github.com/opencontainers/runc/libcontainer/logs"
 	_ "github.com/opencontainers/runc/libcontainer/nsenter"
 	"github.com/sirupsen/logrus"
 )
@@ -19,25 +17,19 @@ func init() {
 		runtime.GOMAXPROCS(1)
 		runtime.LockOSThread()
 
-		level := os.Getenv("_LIBCONTAINER_LOGLEVEL")
-		logLevel, err := logrus.ParseLevel(level)
+		logLevel, err := logrus.ParseLevel(os.Getenv("_LIBCONTAINER_LOGLEVEL"))
 		if err != nil {
-			panic(fmt.Sprintf("libcontainer: failed to parse log level: %q: %v", level, err))
+			panic(err)
 		}
 
-		logPipeFdStr := os.Getenv("_LIBCONTAINER_LOGPIPE")
-		logPipeFd, err := strconv.Atoi(logPipeFdStr)
+		logPipeFd, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_LOGPIPE"))
 		if err != nil {
-			panic(fmt.Sprintf("libcontainer: failed to convert environment variable _LIBCONTAINER_LOGPIPE=%s to int: %s", logPipeFdStr, err))
-		}
-		err = logs.ConfigureLogging(logs.Config{
-			LogPipeFd: logPipeFd,
-			LogFormat: "json",
-			LogLevel:  logLevel,
-		})
-		if err != nil {
-			panic(fmt.Sprintf("libcontainer: failed to configure logging: %v", err))
+			panic(err)
 		}
+
+		logrus.SetLevel(logLevel)
+		logrus.SetOutput(os.NewFile(uintptr(logPipeFd), "logpipe"))
+		logrus.SetFormatter(new(logrus.JSONFormatter))
 		logrus.Debug("child process in init()")
 
 		factory, _ := libcontainer.New("")
diff --git a/kill.go b/kill.go
index e5b13b12..46ea7003 100644
--- a/kill.go
+++ b/kill.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/libcontainer/apparmor/apparmor_unsupported.go b/libcontainer/apparmor/apparmor_unsupported.go
index 684248f2..1adadafe 100644
--- a/libcontainer/apparmor/apparmor_unsupported.go
+++ b/libcontainer/apparmor/apparmor_unsupported.go
@@ -1,4 +1,3 @@
-//go:build !linux
 // +build !linux
 
 package apparmor
diff --git a/libcontainer/capabilities/capabilities.go b/libcontainer/capabilities/capabilities.go
index 7e938d3f..1099d32b 100644
--- a/libcontainer/capabilities/capabilities.go
+++ b/libcontainer/capabilities/capabilities.go
@@ -1,4 +1,3 @@
-//go:build linux
 // +build linux
 
 package capabilities
diff --git a/libcontainer/capabilities/capabilities_unsupported.go b/libcontainer/capabilities/capabilities_unsupported.go
index 0eafa4f2..a3e82ac1 100644
--- a/libcontainer/capabilities/capabilities_unsupported.go
+++ b/libcontainer/capabilities/capabilities_unsupported.go
@@ -1,4 +1,3 @@
-//go:build !linux
 // +build !linux
 
 package capabilities
diff --git a/libcontainer/cgroups/cgroups.go b/libcontainer/cgroups/cgroups.go
index ba2b2266..68a346ca 100644
--- a/libcontainer/cgroups/cgroups.go
+++ b/libcontainer/cgroups/cgroups.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package cgroups
 
 import (
diff --git a/libcontainer/cgroups/cgroups_unsupported.go b/libcontainer/cgroups/cgroups_unsupported.go
new file mode 100644
index 00000000..278d507e
--- /dev/null
+++ b/libcontainer/cgroups/cgroups_unsupported.go
@@ -0,0 +1,3 @@
+// +build !linux
+
+package cgroups
diff --git a/libcontainer/cgroups/devices/devices_emulator.go b/libcontainer/cgroups/devices/devices_emulator.go
index 97ba3f82..668dcd41 100644
--- a/libcontainer/cgroups/devices/devices_emulator.go
+++ b/libcontainer/cgroups/devices/devices_emulator.go
@@ -1,3 +1,5 @@
+// +build linux
+
 // SPDX-License-Identifier: Apache-2.0
 /*
  * Copyright (C) 2020 Aleksa Sarai <cyphar@cyphar.com>
@@ -20,11 +22,12 @@ package devices
 
 import (
 	"bufio"
+	"errors"
 	"fmt"
 	"io"
+	"regexp"
 	"sort"
 	"strconv"
-	"strings"
 
 	"github.com/opencontainers/runc/libcontainer/devices"
 )
@@ -76,21 +79,19 @@ func (e *Emulator) IsAllowAll() bool {
 	return e.IsBlacklist() && len(e.rules) == 0
 }
 
+var devicesListRegexp = regexp.MustCompile(`^([abc])\s+(\d+|\*):(\d+|\*)\s+([rwm]+)$`)
+
 func parseLine(line string) (*deviceRule, error) {
-	// Input: node major:minor perms.
-	fields := strings.FieldsFunc(line, func(r rune) bool {
-		return r == ' ' || r == ':'
-	})
-	if len(fields) != 4 {
-		return nil, fmt.Errorf("malformed devices.list rule %s", line)
+	matches := devicesListRegexp.FindStringSubmatch(line)
+	if matches == nil {
+		return nil, errors.New("line doesn't match devices.list format")
 	}
-
 	var (
 		rule  deviceRule
-		node  = fields[0]
-		major = fields[1]
-		minor = fields[2]
-		perms = fields[3]
+		node  = matches[1]
+		major = matches[2]
+		minor = matches[3]
+		perms = matches[4]
 	)
 
 	// Parse the node type.
@@ -106,6 +107,7 @@ func parseLine(line string) (*deviceRule, error) {
 	case "c":
 		rule.meta.node = devices.CharDevice
 	default:
+		// Should never happen!
 		return nil, fmt.Errorf("unknown device type %q", node)
 	}
 
@@ -134,6 +136,7 @@ func parseLine(line string) (*deviceRule, error) {
 	// Parse the access permissions.
 	rule.perms = devices.Permissions(perms)
 	if !rule.perms.IsValid() || rule.perms.IsEmpty() {
+		// Should never happen!
 		return nil, fmt.Errorf("parse access mode: contained unknown modes or is empty: %q", perms)
 	}
 	return &rule, nil
diff --git a/libcontainer/cgroups/file.go b/libcontainer/cgroups/file.go
index 0cdaf747..b1888f7e 100644
--- a/libcontainer/cgroups/file.go
+++ b/libcontainer/cgroups/file.go
@@ -5,8 +5,6 @@ import (
 	"errors"
 	"fmt"
 	"os"
-	"path"
-	"strconv"
 	"strings"
 	"sync"
 
@@ -15,11 +13,7 @@ import (
 )
 
 // OpenFile opens a cgroup file in a given dir with given flags.
-// It is supposed to be used for cgroup files only, and returns
-// an error if the file is not a cgroup file.
-//
-// Arguments dir and file are joined together to form an absolute path
-// to a file being opened.
+// It is supposed to be used for cgroup files only.
 func OpenFile(dir, file string, flags int) (*os.File, error) {
 	if dir == "" {
 		return nil, fmt.Errorf("no directory specified for %s", file)
@@ -115,6 +109,8 @@ func prepareOpenat2() error {
 	return prepErr
 }
 
+// OpenFile opens a cgroup file in a given dir with given flags.
+// It is supposed to be used for cgroup files only.
 func openFile(dir, file string, flags int) (*os.File, error) {
 	mode := os.FileMode(0)
 	if TestMode && flags&os.O_WRONLY != 0 {
@@ -122,52 +118,34 @@ func openFile(dir, file string, flags int) (*os.File, error) {
 		flags |= os.O_TRUNC | os.O_CREATE
 		mode = 0o600
 	}
-	path := path.Join(dir, file)
 	if prepareOpenat2() != nil {
-		return openFallback(path, flags, mode)
+		return openFallback(dir, file, flags, mode)
 	}
-	relPath := strings.TrimPrefix(path, cgroupfsPrefix)
-	if len(relPath) == len(path) { // non-standard path, old system?
-		return openFallback(path, flags, mode)
+	reldir := strings.TrimPrefix(dir, cgroupfsPrefix)
+	if len(reldir) == len(dir) { // non-standard path, old system?
+		return openFallback(dir, file, flags, mode)
 	}
 
-	fd, err := unix.Openat2(cgroupFd, relPath,
+	relname := reldir + "/" + file
+	fd, err := unix.Openat2(cgroupFd, relname,
 		&unix.OpenHow{
 			Resolve: resolveFlags,
 			Flags:   uint64(flags) | unix.O_CLOEXEC,
 			Mode:    uint64(mode),
 		})
 	if err != nil {
-		err = &os.PathError{Op: "openat2", Path: path, Err: err}
-		// Check if cgroupFd is still opened to cgroupfsDir
-		// (happens when this package is incorrectly used
-		// across the chroot/pivot_root/mntns boundary, or
-		// when /sys/fs/cgroup is remounted).
-		//
-		// TODO: if such usage will ever be common, amend this
-		// to reopen cgroupFd and retry openat2.
-		fdStr := strconv.Itoa(cgroupFd)
-		fdDest, _ := os.Readlink("/proc/self/fd/" + fdStr)
-		if fdDest != cgroupfsDir {
-			// Wrap the error so it is clear that cgroupFd
-			// is opened to an unexpected/wrong directory.
-			err = fmt.Errorf("cgroupFd %s unexpectedly opened to %s != %s: %w",
-				fdStr, fdDest, cgroupfsDir, err)
-		}
-		return nil, err
+		return nil, &os.PathError{Op: "openat2", Path: dir + "/" + file, Err: err}
 	}
 
-	return os.NewFile(uintptr(fd), path), nil
+	return os.NewFile(uintptr(fd), cgroupfsPrefix+relname), nil
 }
 
 var errNotCgroupfs = errors.New("not a cgroup file")
 
-// Can be changed by unit tests.
-var openFallback = openAndCheck
-
-// openAndCheck is used when openat2(2) is not available. It checks the opened
+// openFallback is used when openat2(2) is not available. It checks the opened
 // file is on cgroupfs, returning an error otherwise.
-func openAndCheck(path string, flags int, mode os.FileMode) (*os.File, error) {
+func openFallback(dir, file string, flags int, mode os.FileMode) (*os.File, error) {
+	path := dir + "/" + file
 	fd, err := os.OpenFile(path, flags, mode)
 	if err != nil {
 		return nil, err
diff --git a/libcontainer/cgroups/fs/blkio.go b/libcontainer/cgroups/fs/blkio.go
index 2d764001..5e1b42a8 100644
--- a/libcontainer/cgroups/fs/blkio.go
+++ b/libcontainer/cgroups/fs/blkio.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/cpu.go b/libcontainer/cgroups/fs/cpu.go
index cd81cabc..04bd5bc9 100644
--- a/libcontainer/cgroups/fs/cpu.go
+++ b/libcontainer/cgroups/fs/cpu.go
@@ -1,8 +1,9 @@
+// +build linux
+
 package fs
 
 import (
 	"bufio"
-	"errors"
 	"fmt"
 	"os"
 	"strconv"
@@ -10,7 +11,6 @@ import (
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
 	"github.com/opencontainers/runc/libcontainer/configs"
-	"golang.org/x/sys/unix"
 )
 
 type CpuGroup struct{}
@@ -71,33 +71,15 @@ func (s *CpuGroup) Set(path string, r *configs.Resources) error {
 			return fmt.Errorf("the minimum allowed cpu-shares is %d", sharesRead)
 		}
 	}
-
-	var period string
 	if r.CpuPeriod != 0 {
-		period = strconv.FormatUint(r.CpuPeriod, 10)
-		if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
-			// Sometimes when the period to be set is smaller
-			// than the current one, it is rejected by the kernel
-			// (EINVAL) as old_quota/new_period exceeds the parent
-			// cgroup quota limit. If this happens and the quota is
-			// going to be set, ignore the error for now and retry
-			// after setting the quota.
-			if !errors.Is(err, unix.EINVAL) || r.CpuQuota == 0 {
-				return err
-			}
-		} else {
-			period = ""
+		if err := cgroups.WriteFile(path, "cpu.cfs_period_us", strconv.FormatUint(r.CpuPeriod, 10)); err != nil {
+			return err
 		}
 	}
 	if r.CpuQuota != 0 {
 		if err := cgroups.WriteFile(path, "cpu.cfs_quota_us", strconv.FormatInt(r.CpuQuota, 10)); err != nil {
 			return err
 		}
-		if period != "" {
-			if err := cgroups.WriteFile(path, "cpu.cfs_period_us", period); err != nil {
-				return err
-			}
-		}
 	}
 	return s.SetRtSched(path, r)
 }
diff --git a/libcontainer/cgroups/fs/cpuacct.go b/libcontainer/cgroups/fs/cpuacct.go
index 076704ab..dab4a3db 100644
--- a/libcontainer/cgroups/fs/cpuacct.go
+++ b/libcontainer/cgroups/fs/cpuacct.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/cpuset.go b/libcontainer/cgroups/fs/cpuset.go
index d4edfaaf..8c49a300 100644
--- a/libcontainer/cgroups/fs/cpuset.go
+++ b/libcontainer/cgroups/fs/cpuset.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/devices.go b/libcontainer/cgroups/fs/devices.go
index 2c891704..dcf69ce1 100644
--- a/libcontainer/cgroups/fs/devices.go
+++ b/libcontainer/cgroups/fs/devices.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/freezer.go b/libcontainer/cgroups/fs/freezer.go
index cf1c5efb..4baa2798 100644
--- a/libcontainer/cgroups/fs/freezer.go
+++ b/libcontainer/cgroups/fs/freezer.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/fs.go b/libcontainer/cgroups/fs/fs.go
index 1c1e3f3e..6296719b 100644
--- a/libcontainer/cgroups/fs/fs.go
+++ b/libcontainer/cgroups/fs/fs.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
@@ -29,7 +31,6 @@ var (
 		&NetPrioGroup{},
 		&PerfEventGroup{},
 		&FreezerGroup{},
-		&RdmaGroup{},
 		&NameGroup{GroupName: "name=systemd", Join: true},
 	}
 	HugePageSizes, _ = cgroups.GetHugePageSize()
@@ -195,12 +196,26 @@ func (m *manager) Apply(pid int) (err error) {
 		return cgroups.ErrV1NoUnified
 	}
 
+	m.paths = make(map[string]string)
+	if c.Paths != nil {
+		cgMap, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
+		if err != nil {
+			return err
+		}
+		for name, path := range c.Paths {
+			// XXX(kolyshkin@): why this check is needed?
+			if _, ok := cgMap[name]; ok {
+				m.paths[name] = path
+			}
+		}
+		return cgroups.EnterPid(m.paths, pid)
+	}
+
 	d, err := getCgroupData(m.cgroups, pid)
 	if err != nil {
 		return err
 	}
 
-	m.paths = make(map[string]string)
 	for _, sys := range subsystems {
 		p, err := d.path(sys.Name())
 		if err != nil {
@@ -230,6 +245,9 @@ func (m *manager) Apply(pid int) (err error) {
 }
 
 func (m *manager) Destroy() error {
+	if m.cgroups == nil || m.cgroups.Paths != nil {
+		return nil
+	}
 	m.mu.Lock()
 	defer m.mu.Unlock()
 	return cgroups.RemovePaths(m.paths)
@@ -262,6 +280,11 @@ func (m *manager) Set(r *configs.Resources) error {
 		return nil
 	}
 
+	// If Paths are set, then we are just joining cgroups paths
+	// and there is no need to set any values.
+	if m.cgroups != nil && m.cgroups.Paths != nil {
+		return nil
+	}
 	if r.Unified != nil {
 		return cgroups.ErrV1NoUnified
 	}
diff --git a/libcontainer/cgroups/fs/hugetlb.go b/libcontainer/cgroups/fs/hugetlb.go
index 8477fa6b..3c1b78cb 100644
--- a/libcontainer/cgroups/fs/hugetlb.go
+++ b/libcontainer/cgroups/fs/hugetlb.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/memory.go b/libcontainer/cgroups/fs/memory.go
index 8e10c22b..2c9f1d01 100644
--- a/libcontainer/cgroups/fs/memory.go
+++ b/libcontainer/cgroups/fs/memory.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/name.go b/libcontainer/cgroups/fs/name.go
index ce9f4a0a..94a94b5e 100644
--- a/libcontainer/cgroups/fs/name.go
+++ b/libcontainer/cgroups/fs/name.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/net_cls.go b/libcontainer/cgroups/fs/net_cls.go
index d1364292..f2617aa4 100644
--- a/libcontainer/cgroups/fs/net_cls.go
+++ b/libcontainer/cgroups/fs/net_cls.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/net_prio.go b/libcontainer/cgroups/fs/net_prio.go
index cdc810ba..d0ac5e66 100644
--- a/libcontainer/cgroups/fs/net_prio.go
+++ b/libcontainer/cgroups/fs/net_prio.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/perf_event.go b/libcontainer/cgroups/fs/perf_event.go
index 8ffa83e0..1a306fbe 100644
--- a/libcontainer/cgroups/fs/perf_event.go
+++ b/libcontainer/cgroups/fs/perf_event.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/pids.go b/libcontainer/cgroups/fs/pids.go
index 55bb91fa..83aa6bff 100644
--- a/libcontainer/cgroups/fs/pids.go
+++ b/libcontainer/cgroups/fs/pids.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/rdma.go b/libcontainer/cgroups/fs/rdma.go
deleted file mode 100644
index 5fb25abe..00000000
--- a/libcontainer/cgroups/fs/rdma.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package fs
-
-import (
-	"github.com/opencontainers/runc/libcontainer/cgroups"
-	"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
-	"github.com/opencontainers/runc/libcontainer/configs"
-)
-
-type RdmaGroup struct{}
-
-func (s *RdmaGroup) Name() string {
-	return "rdma"
-}
-
-func (s *RdmaGroup) Apply(path string, d *cgroupData) error {
-	return join(path, d.pid)
-}
-
-func (s *RdmaGroup) Set(path string, r *configs.Resources) error {
-	return fscommon.RdmaSet(path, r)
-}
-
-func (s *RdmaGroup) GetStats(path string, stats *cgroups.Stats) error {
-	return fscommon.RdmaGetStats(path, stats)
-}
diff --git a/libcontainer/cgroups/fs/unsupported.go b/libcontainer/cgroups/fs/unsupported.go
new file mode 100644
index 00000000..3ef9e031
--- /dev/null
+++ b/libcontainer/cgroups/fs/unsupported.go
@@ -0,0 +1,3 @@
+// +build !linux
+
+package fs
diff --git a/libcontainer/cgroups/fs2/cpu.go b/libcontainer/cgroups/fs2/cpu.go
index bbbae4d5..83578a34 100644
--- a/libcontainer/cgroups/fs2/cpu.go
+++ b/libcontainer/cgroups/fs2/cpu.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs2
 
 import (
diff --git a/libcontainer/cgroups/fs2/cpuset.go b/libcontainer/cgroups/fs2/cpuset.go
index 16c45bad..da29d7f2 100644
--- a/libcontainer/cgroups/fs2/cpuset.go
+++ b/libcontainer/cgroups/fs2/cpuset.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs2
 
 import (
diff --git a/libcontainer/cgroups/fs2/defaultpath.go b/libcontainer/cgroups/fs2/defaultpath.go
index ccf89602..3e55e132 100644
--- a/libcontainer/cgroups/fs2/defaultpath.go
+++ b/libcontainer/cgroups/fs2/defaultpath.go
@@ -35,6 +35,10 @@ func defaultDirPath(c *configs.Cgroup) (string, error) {
 	if (c.Name != "" || c.Parent != "") && c.Path != "" {
 		return "", fmt.Errorf("cgroup: either Path or Name and Parent should be used, got %+v", c)
 	}
+	if len(c.Paths) != 0 {
+		// never set by specconv
+		return "", fmt.Errorf("cgroup: Paths is unsupported, use Path, got %+v", c)
+	}
 
 	// XXX: Do not remove this code. Path safety is important! -- cyphar
 	cgPath := libcontainerUtils.CleanPath(c.Path)
diff --git a/libcontainer/cgroups/fs2/devices.go b/libcontainer/cgroups/fs2/devices.go
index 0d234560..5abddb36 100644
--- a/libcontainer/cgroups/fs2/devices.go
+++ b/libcontainer/cgroups/fs2/devices.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs2
 
 import (
diff --git a/libcontainer/cgroups/fs2/freezer.go b/libcontainer/cgroups/fs2/freezer.go
index 8917a641..bdba120d 100644
--- a/libcontainer/cgroups/fs2/freezer.go
+++ b/libcontainer/cgroups/fs2/freezer.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs2
 
 import (
diff --git a/libcontainer/cgroups/fs2/fs2.go b/libcontainer/cgroups/fs2/fs2.go
index e6d38408..6cd9f51f 100644
--- a/libcontainer/cgroups/fs2/fs2.go
+++ b/libcontainer/cgroups/fs2/fs2.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs2
 
 import (
@@ -123,10 +125,6 @@ func (m *manager) GetStats() (*cgroups.Stats, error) {
 	if err := statHugeTlb(m.dirPath, st); err != nil && !os.IsNotExist(err) {
 		errs = append(errs, err)
 	}
-	// rdma (since kernel 4.11)
-	if err := fscommon.RdmaGetStats(m.dirPath, st); err != nil && !os.IsNotExist(err) {
-		errs = append(errs, err)
-	}
 	if len(errs) > 0 && !m.rootless {
 		return st, fmt.Errorf("error while statting cgroup v2: %+v", errs)
 	}
@@ -185,10 +183,6 @@ func (m *manager) Set(r *configs.Resources) error {
 	if err := setHugeTlb(m.dirPath, r); err != nil {
 		return err
 	}
-	// rdma (since kernel 4.11)
-	if err := fscommon.RdmaSet(m.dirPath, r); err != nil {
-		return err
-	}
 	// freezer (since kernel 5.2, pseudo-controller)
 	if err := setFreezer(m.dirPath, r.Freezer); err != nil {
 		return err
diff --git a/libcontainer/cgroups/fs2/hugetlb.go b/libcontainer/cgroups/fs2/hugetlb.go
index aed81a4b..1606f1d6 100644
--- a/libcontainer/cgroups/fs2/hugetlb.go
+++ b/libcontainer/cgroups/fs2/hugetlb.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs2
 
 import (
diff --git a/libcontainer/cgroups/fs2/io.go b/libcontainer/cgroups/fs2/io.go
index b2ff7d34..2771e1ef 100644
--- a/libcontainer/cgroups/fs2/io.go
+++ b/libcontainer/cgroups/fs2/io.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs2
 
 import (
diff --git a/libcontainer/cgroups/fs2/memory.go b/libcontainer/cgroups/fs2/memory.go
index adbc4b23..7ef6b133 100644
--- a/libcontainer/cgroups/fs2/memory.go
+++ b/libcontainer/cgroups/fs2/memory.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs2
 
 import (
diff --git a/libcontainer/cgroups/fs2/pids.go b/libcontainer/cgroups/fs2/pids.go
index c8c4a365..f4c70179 100644
--- a/libcontainer/cgroups/fs2/pids.go
+++ b/libcontainer/cgroups/fs2/pids.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs2
 
 import (
diff --git a/libcontainer/cgroups/fscommon/rdma.go b/libcontainer/cgroups/fscommon/rdma.go
deleted file mode 100644
index d463d15e..00000000
--- a/libcontainer/cgroups/fscommon/rdma.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package fscommon
-
-import (
-	"bufio"
-	"errors"
-	"math"
-	"os"
-	"strconv"
-	"strings"
-
-	"github.com/opencontainers/runc/libcontainer/cgroups"
-	"github.com/opencontainers/runc/libcontainer/configs"
-	"golang.org/x/sys/unix"
-)
-
-// parseRdmaKV parses raw string to RdmaEntry.
-func parseRdmaKV(raw string, entry *cgroups.RdmaEntry) error {
-	var value uint32
-
-	parts := strings.SplitN(raw, "=", 3)
-
-	if len(parts) != 2 {
-		return errors.New("Unable to parse RDMA entry")
-	}
-
-	k, v := parts[0], parts[1]
-
-	if v == "max" {
-		value = math.MaxUint32
-	} else {
-		val64, err := strconv.ParseUint(v, 10, 32)
-		if err != nil {
-			return err
-		}
-		value = uint32(val64)
-	}
-	if k == "hca_handle" {
-		entry.HcaHandles = value
-	} else if k == "hca_object" {
-		entry.HcaObjects = value
-	}
-
-	return nil
-}
-
-// readRdmaEntries reads and converts array of rawstrings to RdmaEntries from file.
-// example entry: mlx4_0 hca_handle=2 hca_object=2000
-func readRdmaEntries(dir, file string) ([]cgroups.RdmaEntry, error) {
-	rdmaEntries := make([]cgroups.RdmaEntry, 0)
-	fd, err := cgroups.OpenFile(dir, file, unix.O_RDONLY)
-	if err != nil {
-		return nil, err
-	}
-	defer fd.Close() //nolint:errorlint
-	scanner := bufio.NewScanner(fd)
-	for scanner.Scan() {
-		parts := strings.SplitN(scanner.Text(), " ", 4)
-		if len(parts) == 3 {
-			entry := new(cgroups.RdmaEntry)
-			entry.Device = parts[0]
-			err = parseRdmaKV(parts[1], entry)
-			if err != nil {
-				continue
-			}
-			err = parseRdmaKV(parts[2], entry)
-			if err != nil {
-				continue
-			}
-
-			rdmaEntries = append(rdmaEntries, *entry)
-		}
-	}
-	return rdmaEntries, scanner.Err()
-}
-
-// RdmaGetStats returns rdma stats such as totalLimit and current entries.
-func RdmaGetStats(path string, stats *cgroups.Stats) error {
-	currentEntries, err := readRdmaEntries(path, "rdma.current")
-	if err != nil {
-		if errors.Is(err, os.ErrNotExist) {
-			err = nil
-		}
-		return err
-	}
-	maxEntries, err := readRdmaEntries(path, "rdma.max")
-	if err != nil {
-		return err
-	}
-	// If device got removed between reading two files, ignore returning stats.
-	if len(currentEntries) != len(maxEntries) {
-		return nil
-	}
-
-	stats.RdmaStats = cgroups.RdmaStats{
-		RdmaLimit:   maxEntries,
-		RdmaCurrent: currentEntries,
-	}
-
-	return nil
-}
-
-func createCmdString(device string, limits configs.LinuxRdma) string {
-	cmdString := device
-	if limits.HcaHandles != nil {
-		cmdString += " hca_handle=" + strconv.FormatUint(uint64(*limits.HcaHandles), 10)
-	}
-	if limits.HcaObjects != nil {
-		cmdString += " hca_object=" + strconv.FormatUint(uint64(*limits.HcaObjects), 10)
-	}
-	return cmdString
-}
-
-// RdmaSet sets RDMA resources.
-func RdmaSet(path string, r *configs.Resources) error {
-	for device, limits := range r.Rdma {
-		if err := cgroups.WriteFile(path, "rdma.max", createCmdString(device, limits)); err != nil {
-			return err
-		}
-	}
-	return nil
-}
diff --git a/libcontainer/cgroups/fscommon/utils.go b/libcontainer/cgroups/fscommon/utils.go
index f4a51c9e..196abf95 100644
--- a/libcontainer/cgroups/fscommon/utils.go
+++ b/libcontainer/cgroups/fscommon/utils.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fscommon
 
 import (
diff --git a/libcontainer/cgroups/getallpids.go b/libcontainer/cgroups/getallpids.go
index 09c01fb6..b218744e 100644
--- a/libcontainer/cgroups/getallpids.go
+++ b/libcontainer/cgroups/getallpids.go
@@ -1,5 +1,4 @@
-//go:build go1.16
-// +build go1.16
+// +build linux,go1.16
 
 package cgroups
 
diff --git a/libcontainer/cgroups/getallpids_go115.go b/libcontainer/cgroups/getallpids_go115.go
index 6b645ef6..5d55a685 100644
--- a/libcontainer/cgroups/getallpids_go115.go
+++ b/libcontainer/cgroups/getallpids_go115.go
@@ -1,5 +1,4 @@
-//go:build !go1.16
-// +build !go1.16
+// +build linux,!go1.16
 
 package cgroups
 
diff --git a/libcontainer/cgroups/stats.go b/libcontainer/cgroups/stats.go
index 40a81dd5..d2e68efe 100644
--- a/libcontainer/cgroups/stats.go
+++ b/libcontainer/cgroups/stats.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package cgroups
 
 type ThrottlingData struct {
@@ -144,17 +146,6 @@ type HugetlbStats struct {
 	Failcnt uint64 `json:"failcnt"`
 }
 
-type RdmaEntry struct {
-	Device     string `json:"device,omitempty"`
-	HcaHandles uint32 `json:"hca_handles,omitempty"`
-	HcaObjects uint32 `json:"hca_objects,omitempty"`
-}
-
-type RdmaStats struct {
-	RdmaLimit   []RdmaEntry `json:"rdma_limit,omitempty"`
-	RdmaCurrent []RdmaEntry `json:"rdma_current,omitempty"`
-}
-
 type Stats struct {
 	CpuStats    CpuStats    `json:"cpu_stats,omitempty"`
 	CPUSetStats CPUSetStats `json:"cpuset_stats,omitempty"`
@@ -163,7 +154,6 @@ type Stats struct {
 	BlkioStats  BlkioStats  `json:"blkio_stats,omitempty"`
 	// the map is in the format "size of hugepage: stats of the hugepage"
 	HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"`
-	RdmaStats    RdmaStats               `json:"rdma_stats,omitempty"`
 }
 
 func NewStats() *Stats {
diff --git a/libcontainer/cgroups/systemd/dbus.go b/libcontainer/cgroups/systemd/dbus.go
index e69469b7..a70a9df4 100644
--- a/libcontainer/cgroups/systemd/dbus.go
+++ b/libcontainer/cgroups/systemd/dbus.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package systemd
 
 import (
diff --git a/libcontainer/cgroups/systemd/unsupported.go b/libcontainer/cgroups/systemd/unsupported.go
new file mode 100644
index 00000000..cb4d00c8
--- /dev/null
+++ b/libcontainer/cgroups/systemd/unsupported.go
@@ -0,0 +1,71 @@
+// +build !linux
+
+package systemd
+
+import (
+	"errors"
+
+	"github.com/opencontainers/runc/libcontainer/cgroups"
+	"github.com/opencontainers/runc/libcontainer/configs"
+)
+
+type Manager struct {
+	Cgroups *configs.Cgroup
+	Paths   map[string]string
+}
+
+func IsRunningSystemd() bool {
+	return false
+}
+
+func NewSystemdCgroupsManager() (func(config *configs.Cgroup, paths map[string]string) cgroups.Manager, error) {
+	return nil, errors.New("Systemd not supported")
+}
+
+func (m *Manager) Apply(pid int) error {
+	return errors.New("Systemd not supported")
+}
+
+func (m *Manager) GetPids() ([]int, error) {
+	return nil, errors.New("Systemd not supported")
+}
+
+func (m *Manager) GetAllPids() ([]int, error) {
+	return nil, errors.New("Systemd not supported")
+}
+
+func (m *Manager) Destroy() error {
+	return errors.New("Systemd not supported")
+}
+
+func (m *Manager) GetPaths() map[string]string {
+	return nil
+}
+
+func (m *Manager) Path(_ string) string {
+	return ""
+}
+
+func (m *Manager) GetStats() (*cgroups.Stats, error) {
+	return nil, errors.New("Systemd not supported")
+}
+
+func (m *Manager) Set(container *configs.Config) error {
+	return errors.New("Systemd not supported")
+}
+
+func (m *Manager) Freeze(state configs.FreezerState) error {
+	return errors.New("Systemd not supported")
+}
+
+func Freeze(c *configs.Cgroup, state configs.FreezerState) error {
+	return errors.New("Systemd not supported")
+}
+
+func (m *Manager) GetCgroups() (*configs.Cgroup, error) {
+	return nil, errors.New("Systemd not supported")
+}
+
+func (m *Manager) Exists() bool {
+	return false
+}
diff --git a/libcontainer/cgroups/systemd/user.go b/libcontainer/cgroups/systemd/user.go
index 0f50f76e..55d97c73 100644
--- a/libcontainer/cgroups/systemd/user.go
+++ b/libcontainer/cgroups/systemd/user.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package systemd
 
 import (
diff --git a/libcontainer/cgroups/systemd/v1.go b/libcontainer/cgroups/systemd/v1.go
index 54266901..cd4720c5 100644
--- a/libcontainer/cgroups/systemd/v1.go
+++ b/libcontainer/cgroups/systemd/v1.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package systemd
 
 import (
@@ -57,7 +59,6 @@ var legacySubsystems = []subsystem{
 	&fs.NetPrioGroup{},
 	&fs.NetClsGroup{},
 	&fs.NameGroup{GroupName: "name=systemd"},
-	&fs.RdmaGroup{},
 }
 
 func genV1ResourcesProperties(r *configs.Resources, cm *dbusConnManager) ([]systemdDbus.Property, error) {
@@ -113,6 +114,21 @@ func (m *legacyManager) Apply(pid int) error {
 
 	m.mu.Lock()
 	defer m.mu.Unlock()
+	if c.Paths != nil {
+		paths := make(map[string]string)
+		cgMap, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
+		if err != nil {
+			return err
+		}
+		// XXX(kolyshkin@): why this check is needed?
+		for name, path := range c.Paths {
+			if _, ok := cgMap[name]; ok {
+				paths[name] = path
+			}
+		}
+		m.paths = paths
+		return cgroups.EnterPid(m.paths, pid)
+	}
 
 	if c.Parent != "" {
 		slice = c.Parent
@@ -186,6 +202,9 @@ func (m *legacyManager) Apply(pid int) error {
 }
 
 func (m *legacyManager) Destroy() error {
+	if m.cgroups.Paths != nil {
+		return nil
+	}
 	m.mu.Lock()
 	defer m.mu.Unlock()
 
@@ -380,6 +399,11 @@ func (m *legacyManager) freezeBeforeSet(unitName string, r *configs.Resources) (
 }
 
 func (m *legacyManager) Set(r *configs.Resources) error {
+	// If Paths are set, then we are just joining cgroups paths
+	// and there is no need to set any values.
+	if m.cgroups.Paths != nil {
+		return nil
+	}
 	if r.Unified != nil {
 		return cgroups.ErrV1NoUnified
 	}
diff --git a/libcontainer/cgroups/systemd/v2.go b/libcontainer/cgroups/systemd/v2.go
index 106720d8..1625dd9a 100644
--- a/libcontainer/cgroups/systemd/v2.go
+++ b/libcontainer/cgroups/systemd/v2.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package systemd
 
 import (
@@ -232,6 +234,10 @@ func (m *unifiedManager) Apply(pid int) error {
 		properties []systemdDbus.Property
 	)
 
+	if c.Paths != nil {
+		return cgroups.WriteCgroupProc(m.path, pid)
+	}
+
 	slice := "system.slice"
 	if m.rootless {
 		slice = "user.slice"
@@ -290,6 +296,9 @@ func (m *unifiedManager) Apply(pid int) error {
 }
 
 func (m *unifiedManager) Destroy() error {
+	if m.cgroups.Paths != nil {
+		return nil
+	}
 	m.mu.Lock()
 	defer m.mu.Unlock()
 
diff --git a/libcontainer/cgroups/utils.go b/libcontainer/cgroups/utils.go
index ec4e7019..b31705be 100644
--- a/libcontainer/cgroups/utils.go
+++ b/libcontainer/cgroups/utils.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package cgroups
 
 import (
diff --git a/libcontainer/configs/cgroup_linux.go b/libcontainer/configs/cgroup_linux.go
index 1432b50b..5ea9d940 100644
--- a/libcontainer/configs/cgroup_linux.go
+++ b/libcontainer/configs/cgroup_linux.go
@@ -28,6 +28,10 @@ type Cgroup struct {
 	// ScopePrefix describes prefix for the scope name
 	ScopePrefix string `json:"scope_prefix"`
 
+	// Paths represent the absolute cgroups paths to join.
+	// This takes precedence over Path.
+	Paths map[string]string
+
 	// Resources contains various cgroups settings to apply
 	*Resources
 
@@ -113,9 +117,6 @@ type Resources struct {
 	// Set class identifier for container's network packets
 	NetClsClassid uint32 `json:"net_cls_classid_u"`
 
-	// Rdma resource restriction configuration
-	Rdma map[string]LinuxRdma `json:"rdma"`
-
 	// Used on cgroups v2:
 
 	// CpuWeight sets a proportional bandwidth limit.
diff --git a/libcontainer/configs/cgroup_unsupported.go b/libcontainer/configs/cgroup_unsupported.go
index 7e383020..2a519f58 100644
--- a/libcontainer/configs/cgroup_unsupported.go
+++ b/libcontainer/configs/cgroup_unsupported.go
@@ -1,4 +1,3 @@
-//go:build !linux
 // +build !linux
 
 package configs
diff --git a/libcontainer/configs/configs_fuzzer.go b/libcontainer/configs/configs_fuzzer.go
index bce829e2..93bf41c8 100644
--- a/libcontainer/configs/configs_fuzzer.go
+++ b/libcontainer/configs/configs_fuzzer.go
@@ -1,4 +1,3 @@
-//go:build gofuzz
 // +build gofuzz
 
 package configs
diff --git a/libcontainer/configs/intelrdt.go b/libcontainer/configs/intelrdt.go
index f8d951ab..57e9f037 100644
--- a/libcontainer/configs/intelrdt.go
+++ b/libcontainer/configs/intelrdt.go
@@ -1,9 +1,6 @@
 package configs
 
 type IntelRdt struct {
-	// The identity for RDT Class of Service
-	ClosID string `json:"closID,omitempty"`
-
 	// The schema for L3 cache id and capacity bitmask (CBM)
 	// Format: "L3:<cache_id0>=<cbm0>;<cache_id1>=<cbm1>;..."
 	L3CacheSchema string `json:"l3_cache_schema,omitempty"`
diff --git a/libcontainer/configs/namespaces_syscall.go b/libcontainer/configs/namespaces_syscall.go
index 0516dba8..2dc7adfc 100644
--- a/libcontainer/configs/namespaces_syscall.go
+++ b/libcontainer/configs/namespaces_syscall.go
@@ -1,4 +1,3 @@
-//go:build linux
 // +build linux
 
 package configs
diff --git a/libcontainer/configs/namespaces_syscall_unsupported.go b/libcontainer/configs/namespaces_syscall_unsupported.go
index fbb0d490..5d9a5c81 100644
--- a/libcontainer/configs/namespaces_syscall_unsupported.go
+++ b/libcontainer/configs/namespaces_syscall_unsupported.go
@@ -1,4 +1,3 @@
-//go:build !linux && !windows
 // +build !linux,!windows
 
 package configs
diff --git a/libcontainer/configs/namespaces_unsupported.go b/libcontainer/configs/namespaces_unsupported.go
index 946db30a..cc76e2f5 100644
--- a/libcontainer/configs/namespaces_unsupported.go
+++ b/libcontainer/configs/namespaces_unsupported.go
@@ -1,4 +1,3 @@
-//go:build !linux
 // +build !linux
 
 package configs
diff --git a/libcontainer/configs/rdma.go b/libcontainer/configs/rdma.go
deleted file mode 100644
index c69f2c80..00000000
--- a/libcontainer/configs/rdma.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package configs
-
-// LinuxRdma for Linux cgroup 'rdma' resource management (Linux 4.11)
-type LinuxRdma struct {
-	// Maximum number of HCA handles that can be opened. Default is "no limit".
-	HcaHandles *uint32 `json:"hca_handles,omitempty"`
-	// Maximum number of HCA objects that can be created. Default is "no limit".
-	HcaObjects *uint32 `json:"hca_objects,omitempty"`
-}
diff --git a/libcontainer/configs/validate/validator.go b/libcontainer/configs/validate/validator.go
index 7d9511c7..c2500a63 100644
--- a/libcontainer/configs/validate/validator.go
+++ b/libcontainer/configs/validate/validator.go
@@ -52,7 +52,7 @@ func (v *ConfigValidator) Validate(config *configs.Config) error {
 	}
 	for _, c := range warns {
 		if err := c(config); err != nil {
-			logrus.WithError(err).Warn("invalid configuration")
+			logrus.WithError(err).Warnf("invalid configuration")
 		}
 	}
 	return nil
@@ -62,17 +62,20 @@ func (v *ConfigValidator) Validate(config *configs.Config) error {
 // to the container's root filesystem.
 func (v *ConfigValidator) rootfs(config *configs.Config) error {
 	if _, err := os.Stat(config.Rootfs); err != nil {
-		return fmt.Errorf("invalid rootfs: %w", err)
+		if os.IsNotExist(err) {
+			return fmt.Errorf("rootfs (%s) does not exist", config.Rootfs)
+		}
+		return err
 	}
 	cleaned, err := filepath.Abs(config.Rootfs)
 	if err != nil {
-		return fmt.Errorf("invalid rootfs: %w", err)
+		return err
 	}
 	if cleaned, err = filepath.EvalSymlinks(cleaned); err != nil {
-		return fmt.Errorf("invalid rootfs: %w", err)
+		return err
 	}
 	if filepath.Clean(config.Rootfs) != cleaned {
-		return errors.New("invalid rootfs: not an absolute path, or a symlink")
+		return fmt.Errorf("%s is not an absolute path or is a symlink", config.Rootfs)
 	}
 	return nil
 }
@@ -173,7 +176,7 @@ func (v *ConfigValidator) sysctl(config *configs.Config) error {
 				hostnet, hostnetErr = isHostNetNS(path)
 			})
 			if hostnetErr != nil {
-				return fmt.Errorf("invalid netns path: %w", hostnetErr)
+				return hostnetErr
 			}
 			if hostnet {
 				return fmt.Errorf("sysctl %q not allowed in host network namespace", s)
@@ -202,16 +205,19 @@ func (v *ConfigValidator) intelrdt(config *configs.Config) error {
 			return errors.New("intelRdt is specified in config, but Intel RDT is not supported or enabled")
 		}
 
-		if config.IntelRdt.ClosID == "." || config.IntelRdt.ClosID == ".." || strings.Contains(config.IntelRdt.ClosID, "/") {
-			return fmt.Errorf("invalid intelRdt.ClosID %q", config.IntelRdt.ClosID)
-		}
-
 		if !intelrdt.IsCATEnabled() && config.IntelRdt.L3CacheSchema != "" {
 			return errors.New("intelRdt.l3CacheSchema is specified in config, but Intel RDT/CAT is not enabled")
 		}
 		if !intelrdt.IsMBAEnabled() && config.IntelRdt.MemBwSchema != "" {
 			return errors.New("intelRdt.memBwSchema is specified in config, but Intel RDT/MBA is not enabled")
 		}
+
+		if intelrdt.IsCATEnabled() && config.IntelRdt.L3CacheSchema == "" {
+			return errors.New("Intel RDT/CAT is enabled and intelRdt is specified in config, but intelRdt.l3CacheSchema is empty")
+		}
+		if intelrdt.IsMBAEnabled() && config.IntelRdt.MemBwSchema == "" {
+			return errors.New("Intel RDT/MBA is enabled and intelRdt is specified in config, but intelRdt.memBwSchema is empty")
+		}
 	}
 
 	return nil
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index 420b8f47..21e77932 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
@@ -229,7 +231,7 @@ func (c *linuxContainer) Start(process *Process) error {
 	c.m.Lock()
 	defer c.m.Unlock()
 	if c.config.Cgroups.Resources.SkipDevices {
-		return errors.New("can't start container with SkipDevices set")
+		return &ConfigError{"can't start container with SkipDevices set"}
 	}
 	if process.Init {
 		if err := c.createExecFifo(); err != nil {
@@ -1900,10 +1902,9 @@ func (c *linuxContainer) currentState() (*State, error) {
 		startTime, _ = c.initProcess.startTime()
 		externalDescriptors = c.initProcess.externalDescriptors()
 	}
-
-	intelRdtPath := ""
-	if c.intelRdtManager != nil {
-		intelRdtPath = c.intelRdtManager.GetPath()
+	intelRdtPath, err := intelrdt.GetIntelRdtPath(c.ID())
+	if err != nil {
+		intelRdtPath = ""
 	}
 	state := &State{
 		BaseState: BaseState{
diff --git a/libcontainer/devices/device_unix.go b/libcontainer/devices/device_unix.go
index e2af4ff2..9369c6f7 100644
--- a/libcontainer/devices/device_unix.go
+++ b/libcontainer/devices/device_unix.go
@@ -1,4 +1,3 @@
-//go:build !windows
 // +build !windows
 
 package devices
diff --git a/libcontainer/error.go b/libcontainer/error.go
index 510c0722..bd5ad1f8 100644
--- a/libcontainer/error.go
+++ b/libcontainer/error.go
@@ -11,3 +11,11 @@ var (
 	ErrNotRunning = errors.New("container not running")
 	ErrNotPaused  = errors.New("container not paused")
 )
+
+type ConfigError struct {
+	details string
+}
+
+func (e *ConfigError) Error() string {
+	return "invalid configuration: " + e.details
+}
diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go
index ff572781..63cf57b0 100644
--- a/libcontainer/factory_linux.go
+++ b/libcontainer/factory_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
@@ -249,13 +251,13 @@ type LinuxFactory struct {
 
 func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) {
 	if l.Root == "" {
-		return nil, errors.New("root not set")
+		return nil, &ConfigError{"invalid root"}
 	}
 	if err := l.validateID(id); err != nil {
 		return nil, err
 	}
 	if err := l.Validator.Validate(config); err != nil {
-		return nil, err
+		return nil, &ConfigError{err.Error()}
 	}
 	containerRoot, err := securejoin.SecureJoin(l.Root, id)
 	if err != nil {
@@ -292,7 +294,7 @@ func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, err
 
 func (l *LinuxFactory) Load(id string) (Container, error) {
 	if l.Root == "" {
-		return nil, errors.New("root not set")
+		return nil, &ConfigError{"invalid root"}
 	}
 	// when load, we need to check id is valid or not.
 	if err := l.validateID(id); err != nil {
diff --git a/libcontainer/init_linux.go b/libcontainer/init_linux.go
index 5bbe2920..985c9250 100644
--- a/libcontainer/init_linux.go
+++ b/libcontainer/init_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/intelrdt/intelrdt.go b/libcontainer/intelrdt/intelrdt.go
index dcf231b6..9c32aabd 100644
--- a/libcontainer/intelrdt/intelrdt.go
+++ b/libcontainer/intelrdt/intelrdt.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package intelrdt
 
 import (
@@ -70,7 +72,7 @@ import (
  * |-- ...
  * |-- schemata
  * |-- tasks
- * |-- <clos>
+ * |-- <container_id>
  *     |-- ...
  *     |-- schemata
  *     |-- tasks
@@ -153,7 +155,7 @@ type Manager interface {
 	// Returns statistics for Intel RDT
 	GetStats() (*Stats, error)
 
-	// Destroys the Intel RDT container-specific 'container_id' group
+	// Destroys the Intel RDT 'container_id' group
 	Destroy() error
 
 	// Returns Intel RDT path to save in a state file and to be able to
@@ -203,7 +205,9 @@ var (
 )
 
 type intelRdtData struct {
+	root   string
 	config *configs.Config
+	pid    int
 }
 
 // Check if Intel RDT sub-features are enabled in featuresInit()
@@ -401,6 +405,18 @@ func writeFile(dir, file, data string) error {
 	return nil
 }
 
+func getIntelRdtData(c *configs.Config, pid int) (*intelRdtData, error) {
+	rootPath, err := getIntelRdtRoot()
+	if err != nil {
+		return nil, err
+	}
+	return &intelRdtData{
+		root:   rootPath,
+		config: c,
+		pid:    pid,
+	}, nil
+}
+
 // Get the read-only L3 cache information
 func getL3CacheInfo() (*L3CacheInfo, error) {
 	l3CacheInfo := &L3CacheInfo{}
@@ -515,19 +531,15 @@ func IsMBAScEnabled() bool {
 	return mbaScEnabled
 }
 
-// Get the path of the clos group in "resource control" filesystem that the container belongs to
-func (m *intelRdtManager) getIntelRdtPath() (string, error) {
+// Get the 'container_id' path in Intel RDT "resource control" filesystem
+func GetIntelRdtPath(id string) (string, error) {
 	rootPath, err := getIntelRdtRoot()
 	if err != nil {
 		return "", err
 	}
 
-	clos := m.id
-	if m.config.IntelRdt != nil && m.config.IntelRdt.ClosID != "" {
-		clos = m.config.IntelRdt.ClosID
-	}
-
-	return filepath.Join(rootPath, clos), nil
+	path := filepath.Join(rootPath, id)
+	return path, nil
 }
 
 // Applies Intel RDT configuration to the process with the specified pid
@@ -536,48 +548,30 @@ func (m *intelRdtManager) Apply(pid int) (err error) {
 	if m.config.IntelRdt == nil {
 		return nil
 	}
-
-	path, err := m.getIntelRdtPath()
+	d, err := getIntelRdtData(m.config, pid)
 	if err != nil {
 		return err
 	}
 
 	m.mu.Lock()
 	defer m.mu.Unlock()
-
-	if m.config.IntelRdt.ClosID != "" && m.config.IntelRdt.L3CacheSchema == "" && m.config.IntelRdt.MemBwSchema == "" {
-		// Check that the CLOS exists, i.e. it has been pre-configured to
-		// conform with the runtime spec
-		if _, err := os.Stat(path); err != nil {
-			return fmt.Errorf("clos dir not accessible (must be pre-created when l3CacheSchema and memBwSchema are empty): %w", err)
-		}
-	}
-
-	if err := os.MkdirAll(path, 0o755); err != nil {
-		return newLastCmdError(err)
-	}
-
-	if err := WriteIntelRdtTasks(path, pid); err != nil {
-		return newLastCmdError(err)
+	path, err := d.join(m.id)
+	if err != nil {
+		return err
 	}
 
 	m.path = path
 	return nil
 }
 
-// Destroys the Intel RDT container-specific 'container_id' group
+// Destroys the Intel RDT 'container_id' group
 func (m *intelRdtManager) Destroy() error {
-	// Don't remove resctrl group if closid has been explicitly specified. The
-	// group is likely externally managed, i.e. by some other entity than us.
-	// There are probably other containers/tasks sharing the same group.
-	if m.config.IntelRdt == nil || m.config.IntelRdt.ClosID == "" {
-		m.mu.Lock()
-		defer m.mu.Unlock()
-		if err := os.RemoveAll(m.GetPath()); err != nil {
-			return err
-		}
-		m.path = ""
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if err := os.RemoveAll(m.GetPath()); err != nil {
+		return err
 	}
+	m.path = ""
 	return nil
 }
 
@@ -585,7 +579,7 @@ func (m *intelRdtManager) Destroy() error {
 // restore the object later
 func (m *intelRdtManager) GetPath() string {
 	if m.path == "" {
-		m.path, _ = m.getIntelRdtPath()
+		m.path, _ = GetIntelRdtPath(m.id)
 	}
 	return m.path
 }
@@ -612,7 +606,7 @@ func (m *intelRdtManager) GetStats() (*Stats, error) {
 	}
 	schemaRootStrings := strings.Split(tmpRootStrings, "\n")
 
-	// The L3 cache and memory bandwidth schemata in container's clos group
+	// The L3 cache and memory bandwidth schemata in 'container_id' group
 	containerPath := m.GetPath()
 	tmpStrings, err := getIntelRdtParamString(containerPath, "schemata")
 	if err != nil {
@@ -635,7 +629,7 @@ func (m *intelRdtManager) GetStats() (*Stats, error) {
 			}
 		}
 
-		// The L3 cache schema in container's clos group
+		// The L3 cache schema in 'container_id' group
 		for _, schema := range schemaStrings {
 			if strings.Contains(schema, "L3") {
 				stats.L3CacheSchema = strings.TrimSpace(schema)
@@ -658,7 +652,7 @@ func (m *intelRdtManager) GetStats() (*Stats, error) {
 			}
 		}
 
-		// The memory bandwidth schema in container's clos group
+		// The memory bandwidth schema in 'container_id' group
 		for _, schema := range schemaStrings {
 			if strings.Contains(schema, "MB") {
 				stats.MemBwSchema = strings.TrimSpace(schema)
@@ -728,12 +722,6 @@ func (m *intelRdtManager) Set(container *configs.Config) error {
 		l3CacheSchema := container.IntelRdt.L3CacheSchema
 		memBwSchema := container.IntelRdt.MemBwSchema
 
-		// TODO: verify that l3CacheSchema and/or memBwSchema match the
-		// existing schemata if ClosID has been specified. This is a more
-		// involved than reading the file and doing plain string comparison as
-		// the value written in does not necessarily match what gets read out
-		// (leading zeros, cache id ordering etc).
-
 		// Write a single joint schema string to schemata file
 		if l3CacheSchema != "" && memBwSchema != "" {
 			if err := writeFile(path, "schemata", l3CacheSchema+"\n"+memBwSchema); err != nil {
@@ -759,6 +747,18 @@ func (m *intelRdtManager) Set(container *configs.Config) error {
 	return nil
 }
 
+func (raw *intelRdtData) join(id string) (string, error) {
+	path := filepath.Join(raw.root, id)
+	if err := os.MkdirAll(path, 0o755); err != nil {
+		return "", newLastCmdError(err)
+	}
+
+	if err := WriteIntelRdtTasks(path, raw.pid); err != nil {
+		return "", err
+	}
+	return path, nil
+}
+
 func newLastCmdError(err error) error {
 	status, err1 := getLastCmdStatus()
 	if err1 == nil {
diff --git a/libcontainer/intelrdt/mbm.go b/libcontainer/intelrdt/mbm.go
index 13f31ac7..0fbecdeb 100644
--- a/libcontainer/intelrdt/mbm.go
+++ b/libcontainer/intelrdt/mbm.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package intelrdt
 
 // The flag to indicate if Intel RDT/MBM is enabled
diff --git a/libcontainer/intelrdt/stats.go b/libcontainer/intelrdt/stats.go
index a5eb2541..4f3bd84e 100644
--- a/libcontainer/intelrdt/stats.go
+++ b/libcontainer/intelrdt/stats.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package intelrdt
 
 type L3CacheInfo struct {
diff --git a/libcontainer/keys/keyctl.go b/libcontainer/keys/keyctl.go
index f3a6c534..412b724e 100644
--- a/libcontainer/keys/keyctl.go
+++ b/libcontainer/keys/keyctl.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package keys
 
 import (
diff --git a/libcontainer/logs/logs.go b/libcontainer/logs/logs.go
index 07592176..95deb0d6 100644
--- a/libcontainer/logs/logs.go
+++ b/libcontainer/logs/logs.go
@@ -3,37 +3,28 @@ package logs
 import (
 	"bufio"
 	"encoding/json"
-	"errors"
-	"fmt"
 	"io"
-	"os"
-	"sync"
 
 	"github.com/sirupsen/logrus"
 )
 
-var (
-	configureMutex sync.Mutex
-	// loggingConfigured will be set once logging has been configured via invoking `ConfigureLogging`.
-	// Subsequent invocations of `ConfigureLogging` would be no-op
-	loggingConfigured = false
-)
-
-type Config struct {
-	LogLevel    logrus.Level
-	LogFormat   string
-	LogFilePath string
-	LogPipeFd   int
-	LogCaller   bool
-}
-
 func ForwardLogs(logPipe io.ReadCloser) chan error {
 	done := make(chan error, 1)
 	s := bufio.NewScanner(logPipe)
 
+	logger := logrus.StandardLogger()
+	if logger.ReportCaller {
+		// Need a copy of the standard logger, but with ReportCaller
+		// turned off, as the logs are merely forwarded and their
+		// true source is not this file/line/function.
+		logNoCaller := *logrus.StandardLogger()
+		logNoCaller.ReportCaller = false
+		logger = &logNoCaller
+	}
+
 	go func() {
 		for s.Scan() {
-			processEntry(s.Bytes())
+			processEntry(s.Bytes(), logger)
 		}
 		if err := logPipe.Close(); err != nil {
 			logrus.Errorf("error closing log source: %v", err)
@@ -47,60 +38,19 @@ func ForwardLogs(logPipe io.ReadCloser) chan error {
 	return done
 }
 
-func processEntry(text []byte) {
+func processEntry(text []byte, logger *logrus.Logger) {
 	if len(text) == 0 {
 		return
 	}
 
 	var jl struct {
-		Level string `json:"level"`
-		Msg   string `json:"msg"`
+		Level logrus.Level `json:"level"`
+		Msg   string       `json:"msg"`
 	}
 	if err := json.Unmarshal(text, &jl); err != nil {
 		logrus.Errorf("failed to decode %q to json: %v", text, err)
 		return
 	}
 
-	lvl, err := logrus.ParseLevel(jl.Level)
-	if err != nil {
-		logrus.Errorf("failed to parse log level %q: %v", jl.Level, err)
-		return
-	}
-	logrus.StandardLogger().Logf(lvl, jl.Msg)
-}
-
-func ConfigureLogging(config Config) error {
-	configureMutex.Lock()
-	defer configureMutex.Unlock()
-
-	if loggingConfigured {
-		return errors.New("logging has already been configured")
-	}
-
-	logrus.SetLevel(config.LogLevel)
-	logrus.SetReportCaller(config.LogCaller)
-
-	// XXX: while 0 is a valid fd (usually stdin), here we assume
-	// that we never deliberately set LogPipeFd to 0.
-	if config.LogPipeFd > 0 {
-		logrus.SetOutput(os.NewFile(uintptr(config.LogPipeFd), "logpipe"))
-	} else if config.LogFilePath != "" {
-		f, err := os.OpenFile(config.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0o644)
-		if err != nil {
-			return err
-		}
-		logrus.SetOutput(f)
-	}
-
-	switch config.LogFormat {
-	case "text":
-		// retain logrus's default.
-	case "json":
-		logrus.SetFormatter(new(logrus.JSONFormatter))
-	default:
-		return fmt.Errorf("unknown log-format %q", config.LogFormat)
-	}
-
-	loggingConfigured = true
-	return nil
+	logger.Log(jl.Level, jl.Msg)
 }
diff --git a/libcontainer/message_linux.go b/libcontainer/message_linux.go
index f10efa36..1d4f5033 100644
--- a/libcontainer/message_linux.go
+++ b/libcontainer/message_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/network_linux.go b/libcontainer/network_linux.go
index aa2be8ff..12e5800f 100644
--- a/libcontainer/network_linux.go
+++ b/libcontainer/network_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/notify_linux.go b/libcontainer/notify_linux.go
index 0ec1a55c..73a6f594 100644
--- a/libcontainer/notify_linux.go
+++ b/libcontainer/notify_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/notify_v2_linux.go b/libcontainer/notify_linux_v2.go
similarity index 99%
rename from libcontainer/notify_v2_linux.go
rename to libcontainer/notify_linux_v2.go
index 821536c8..8e3d4fd0 100644
--- a/libcontainer/notify_v2_linux.go
+++ b/libcontainer/notify_linux_v2.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/nsenter/nsenter.go b/libcontainer/nsenter/nsenter.go
index 2d1f3e11..07f4d63e 100644
--- a/libcontainer/nsenter/nsenter.go
+++ b/libcontainer/nsenter/nsenter.go
@@ -1,4 +1,3 @@
-//go:build linux && !gccgo
 // +build linux,!gccgo
 
 package nsenter
diff --git a/libcontainer/nsenter/nsenter_gccgo.go b/libcontainer/nsenter/nsenter_gccgo.go
index 86bad539..63c7a3ec 100644
--- a/libcontainer/nsenter/nsenter_gccgo.go
+++ b/libcontainer/nsenter/nsenter_gccgo.go
@@ -1,4 +1,3 @@
-//go:build linux && gccgo
 // +build linux,gccgo
 
 package nsenter
diff --git a/libcontainer/nsenter/nsenter_unsupported.go b/libcontainer/nsenter/nsenter_unsupported.go
index a7e049c6..2459c636 100644
--- a/libcontainer/nsenter/nsenter_unsupported.go
+++ b/libcontainer/nsenter/nsenter_unsupported.go
@@ -1,4 +1,3 @@
-//go:build !linux || !cgo
 // +build !linux !cgo
 
 package nsenter
diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c
index 30b6d5e4..23861a84 100644
--- a/libcontainer/nsenter/nsexec.c
+++ b/libcontainer/nsenter/nsexec.c
@@ -1091,7 +1091,7 @@ void nsexec(void)
 			write_log(DEBUG, "signal completion to stage-0");
 			s = SYNC_CHILD_FINISH;
 			if (write(syncfd, &s, sizeof(s)) != sizeof(s))
-				bail("failed to sync with parent: write(SYNC_CHILD_FINISH)");
+				bail("failed to sync with patent: write(SYNC_CHILD_FINISH)");
 
 			/* Close sync pipes. */
 			if (close(sync_grandchild_pipe[0]) < 0)
diff --git a/libcontainer/process_linux.go b/libcontainer/process_linux.go
index ba025812..e1649876 100644
--- a/libcontainer/process_linux.go
+++ b/libcontainer/process_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/restored_process.go b/libcontainer/restored_process.go
index cdffbd3a..6987c8fd 100644
--- a/libcontainer/restored_process.go
+++ b/libcontainer/restored_process.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
index 19bc96d5..3c3fc0d1 100644
--- a/libcontainer/rootfs_linux.go
+++ b/libcontainer/rootfs_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/seccomp/patchbpf/enosys_linux.go b/libcontainer/seccomp/patchbpf/enosys_linux.go
index 37a05b5a..ccf929d7 100644
--- a/libcontainer/seccomp/patchbpf/enosys_linux.go
+++ b/libcontainer/seccomp/patchbpf/enosys_linux.go
@@ -1,5 +1,4 @@
-//go:build cgo && seccomp
-// +build cgo,seccomp
+// +build linux,cgo,seccomp
 
 package patchbpf
 
diff --git a/libcontainer/seccomp/patchbpf/enosys_unsupported.go b/libcontainer/seccomp/patchbpf/enosys_unsupported.go
index d23167ae..682131e4 100644
--- a/libcontainer/seccomp/patchbpf/enosys_unsupported.go
+++ b/libcontainer/seccomp/patchbpf/enosys_unsupported.go
@@ -1,4 +1,3 @@
-//go:build !linux || !cgo || !seccomp
 // +build !linux !cgo !seccomp
 
 package patchbpf
diff --git a/libcontainer/seccomp/seccomp_linux.go b/libcontainer/seccomp/seccomp_linux.go
index f4625082..58cad902 100644
--- a/libcontainer/seccomp/seccomp_linux.go
+++ b/libcontainer/seccomp/seccomp_linux.go
@@ -1,5 +1,4 @@
-//go:build cgo && seccomp
-// +build cgo,seccomp
+// +build linux,cgo,seccomp
 
 package seccomp
 
diff --git a/libcontainer/seccomp/seccomp_unsupported.go b/libcontainer/seccomp/seccomp_unsupported.go
index 6e593dbe..8b7973e9 100644
--- a/libcontainer/seccomp/seccomp_unsupported.go
+++ b/libcontainer/seccomp/seccomp_unsupported.go
@@ -1,4 +1,3 @@
-//go:build !linux || !cgo || !seccomp
 // +build !linux !cgo !seccomp
 
 package seccomp
diff --git a/libcontainer/setns_init_linux.go b/libcontainer/setns_init_linux.go
index 09a7d895..89b5609a 100644
--- a/libcontainer/setns_init_linux.go
+++ b/libcontainer/setns_init_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go
index f0d3506e..89172316 100644
--- a/libcontainer/specconv/spec_linux.go
+++ b/libcontainer/specconv/spec_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 // Package specconv implements conversion of specifications to libcontainer
 // configurations
 package specconv
@@ -299,7 +301,6 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
 		}
 		if spec.Linux.IntelRdt != nil {
 			config.IntelRdt = &configs.IntelRdt{
-				ClosID:        spec.Linux.IntelRdt.ClosID,
 				L3CacheSchema: spec.Linux.IntelRdt.L3CacheSchema,
 				MemBwSchema:   spec.Linux.IntelRdt.MemBwSchema,
 			}
@@ -606,15 +607,6 @@ func CreateCgroupConfig(opts *CreateOpts, defaultDevs []*devices.Device) (*confi
 					Limit:    l.Limit,
 				})
 			}
-			if len(r.Rdma) > 0 {
-				c.Resources.Rdma = make(map[string]configs.LinuxRdma, len(r.Rdma))
-				for k, v := range r.Rdma {
-					c.Resources.Rdma[k] = configs.LinuxRdma{
-						HcaHandles: v.HcaHandles,
-						HcaObjects: v.HcaObjects,
-					}
-				}
-			}
 			if r.Network != nil {
 				if r.Network.ClassID != nil {
 					c.Resources.NetClsClassid = *r.Network.ClassID
diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go
index c02f0c45..f1e20207 100644
--- a/libcontainer/standard_init_linux.go
+++ b/libcontainer/standard_init_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/state_linux.go b/libcontainer/state_linux.go
index aa6259b1..3b71a0b0 100644
--- a/libcontainer/state_linux.go
+++ b/libcontainer/state_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/system/linux.go b/libcontainer/system/linux.go
index bb612daa..b9fd0832 100644
--- a/libcontainer/system/linux.go
+++ b/libcontainer/system/linux.go
@@ -1,4 +1,3 @@
-//go:build linux
 // +build linux
 
 package system
diff --git a/libcontainer/system/syscall_linux_32.go b/libcontainer/system/syscall_linux_32.go
index 1acc5cb0..c5ca5d86 100644
--- a/libcontainer/system/syscall_linux_32.go
+++ b/libcontainer/system/syscall_linux_32.go
@@ -1,4 +1,3 @@
-//go:build linux && (386 || arm)
 // +build linux
 // +build 386 arm
 
diff --git a/libcontainer/system/syscall_linux_64.go b/libcontainer/system/syscall_linux_64.go
index 1ed0dba1..e05e30ad 100644
--- a/libcontainer/system/syscall_linux_64.go
+++ b/libcontainer/system/syscall_linux_64.go
@@ -1,4 +1,3 @@
-//go:build linux && (arm64 || amd64 || mips || mipsle || mips64 || mips64le || ppc || ppc64 || ppc64le || riscv64 || s390x)
 // +build linux
 // +build arm64 amd64 mips mipsle mips64 mips64le ppc ppc64 ppc64le riscv64 s390x
 
diff --git a/libcontainer/user/lookup_unix.go b/libcontainer/user/lookup_unix.go
index f95c1409..967717a1 100644
--- a/libcontainer/user/lookup_unix.go
+++ b/libcontainer/user/lookup_unix.go
@@ -1,4 +1,3 @@
-//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
 // +build darwin dragonfly freebsd linux netbsd openbsd solaris
 
 package user
diff --git a/libcontainer/user/user_fuzzer.go b/libcontainer/user/user_fuzzer.go
index e018eae6..8c9bb5df 100644
--- a/libcontainer/user/user_fuzzer.go
+++ b/libcontainer/user/user_fuzzer.go
@@ -1,4 +1,3 @@
-//go:build gofuzz
 // +build gofuzz
 
 package user
diff --git a/libcontainer/userns/userns_fuzzer.go b/libcontainer/userns/userns_fuzzer.go
index 1e00ab8b..529f8eae 100644
--- a/libcontainer/userns/userns_fuzzer.go
+++ b/libcontainer/userns/userns_fuzzer.go
@@ -1,4 +1,3 @@
-//go:build gofuzz
 // +build gofuzz
 
 package userns
diff --git a/libcontainer/userns/userns_unsupported.go b/libcontainer/userns/userns_unsupported.go
index f35c13a1..f45bb0c3 100644
--- a/libcontainer/userns/userns_unsupported.go
+++ b/libcontainer/userns/userns_unsupported.go
@@ -1,4 +1,3 @@
-//go:build !linux
 // +build !linux
 
 package userns
diff --git a/libcontainer/utils/cmsg.go b/libcontainer/utils/cmsg.go
index 52a61655..c8a9364d 100644
--- a/libcontainer/utils/cmsg.go
+++ b/libcontainer/utils/cmsg.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package utils
 
 /*
diff --git a/libcontainer/utils/utils_unix.go b/libcontainer/utils/utils_unix.go
index 3b303e18..387ae509 100644
--- a/libcontainer/utils/utils_unix.go
+++ b/libcontainer/utils/utils_unix.go
@@ -1,4 +1,3 @@
-//go:build !windows
 // +build !windows
 
 package utils
diff --git a/list.go b/list.go
index 38d9869d..5a1c0850 100644
--- a/list.go
+++ b/list.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/main.go b/main.go
index f141e79b..bb69672f 100644
--- a/main.go
+++ b/main.go
@@ -1,13 +1,15 @@
 package main
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"os"
+	"path/filepath"
 	"runtime"
+	"strconv"
 	"strings"
 
-	"github.com/opencontainers/runc/libcontainer/logs"
 	"github.com/opencontainers/runc/libcontainer/seccomp"
 	"github.com/opencontainers/runtime-spec/specs-go"
 
@@ -149,7 +151,7 @@ func main() {
 			return err
 		}
 
-		return logs.ConfigureLogging(createLogConfig(context))
+		return configLogrus(context)
 	}
 
 	// If the command returns an error, cli takes upon itself to print
@@ -173,22 +175,42 @@ func (f *FatalWriter) Write(p []byte) (n int, err error) {
 	return len(p), nil
 }
 
-func createLogConfig(context *cli.Context) logs.Config {
-	logFilePath := context.GlobalString("log")
-	logPipeFd := 0
-	if logFilePath == "" {
-		logPipeFd = 2
+func configLogrus(context *cli.Context) error {
+	if context.GlobalBool("debug") {
+		logrus.SetLevel(logrus.DebugLevel)
+		logrus.SetReportCaller(true)
+		// Shorten function and file names reported by the logger, by
+		// trimming common "github.com/opencontainers/runc" prefix.
+		// This is only done for text formatter.
+		_, file, _, _ := runtime.Caller(0)
+		prefix := filepath.Dir(file) + "/"
+		logrus.SetFormatter(&logrus.TextFormatter{
+			CallerPrettyfier: func(f *runtime.Frame) (string, string) {
+				function := strings.TrimPrefix(f.Function, prefix) + "()"
+				fileLine := strings.TrimPrefix(f.File, prefix) + ":" + strconv.Itoa(f.Line)
+				return function, fileLine
+			},
+		})
 	}
-	config := logs.Config{
-		LogPipeFd:   logPipeFd,
-		LogLevel:    logrus.InfoLevel,
-		LogFilePath: logFilePath,
-		LogFormat:   context.GlobalString("log-format"),
-		LogCaller:   context.GlobalBool("debug"),
+
+	switch f := context.GlobalString("log-format"); f {
+	case "":
+		// do nothing
+	case "text":
+		// do nothing
+	case "json":
+		logrus.SetFormatter(new(logrus.JSONFormatter))
+	default:
+		return errors.New("invalid log-format: " + f)
 	}
-	if context.GlobalBool("debug") {
-		config.LogLevel = logrus.DebugLevel
+
+	if file := context.GlobalString("log"); file != "" {
+		f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0o644)
+		if err != nil {
+			return err
+		}
+		logrus.SetOutput(f)
 	}
 
-	return config
+	return nil
 }
diff --git a/man/runc-run.8.md b/man/runc-run.8.md
index 49594697..216649ae 100644
--- a/man/runc-run.8.md
+++ b/man/runc-run.8.md
@@ -42,12 +42,6 @@ container to inherit the calling processes session key.
 : Pass _N_ additional file descriptors to the container (**stdio** +
 **$LISTEN_FDS** + _N_ in total). Default is **0**.
 
-**--keep**
-: Keep container's state directory and cgroup. This can be helpful if a user
-wants to check the state (e.g. of cgroup controllers) after the container has
-exited. If this option is used, a manual **runc delete** is needed afterwards
-to clean an exited container's artefacts.
-
 # SEE ALSO
 
 **runc**(8).
diff --git a/notify_socket.go b/notify_socket.go
index 00ca672d..e578dbf8 100644
--- a/notify_socket.go
+++ b/notify_socket.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/pause.go b/pause.go
index a7f0aacc..224c79f3 100644
--- a/pause.go
+++ b/pause.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/ps.go b/ps.go
index 4083e559..cbbb670b 100644
--- a/ps.go
+++ b/ps.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/restore.go b/restore.go
index 4e2841ba..f7081e4c 100644
--- a/restore.go
+++ b/restore.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/rootless_linux.go b/rootless_linux.go
index ae017033..3c210538 100644
--- a/rootless_linux.go
+++ b/rootless_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/run.go b/run.go
index c3b18471..f8d63178 100644
--- a/run.go
+++ b/run.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
@@ -38,10 +40,6 @@ command(s) that get executed on start, edit the args parameter of the spec. See
 			Name:  "detach, d",
 			Usage: "detach from the container's process",
 		},
-		cli.BoolFlag{
-			Name:  "keep",
-			Usage: "do not delete the container after it exits",
-		},
 		cli.StringFlag{
 			Name:  "pid-file",
 			Value: "",
diff --git a/signals.go b/signals.go
index 2555b765..250764c7 100644
--- a/signals.go
+++ b/signals.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/spec.go b/spec.go
index 87d6ebea..4dc4c028 100644
--- a/spec.go
+++ b/spec.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/state.go b/state.go
index b645e5ab..718813c3 100644
--- a/state.go
+++ b/state.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/tty.go b/tty.go
index 10106a95..31064384 100644
--- a/tty.go
+++ b/tty.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/update.go b/update.go
index d02e7af9..7db5879d 100644
--- a/update.go
+++ b/update.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
diff --git a/utils_linux.go b/utils_linux.go
index 707bf5d3..8b8d9e57 100644
--- a/utils_linux.go
+++ b/utils_linux.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package main
 
 import (
@@ -437,7 +439,7 @@ func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOp
 
 	r := &runner{
 		enableSubreaper: !context.Bool("no-subreaper"),
-		shouldDestroy:   !context.Bool("keep"),
+		shouldDestroy:   true,
 		container:       container,
 		listenFDs:       listenFDs,
 		notifySocket:    notifySocket,


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2646c449..591a1870 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,10 +11,6 @@ on:
       - release-*
   pull_request:
 
-env:
-  # Don't ignore C warnings. Note that the output of "go env CGO_CFLAGS" by default is "-g -O2", so we keep them.
-  CGO_CFLAGS: -g -O2 -Werror
-
 jobs:
   test:
     runs-on: ubuntu-20.04
diff --git a/libcontainer/cgroups/cgroups_test.go b/libcontainer/cgroups/cgroups_test.go
index b31412f5..9efb83ec 100644
--- a/libcontainer/cgroups/cgroups_test.go
+++ b/libcontainer/cgroups/cgroups_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package cgroups
 
 import (
diff --git a/libcontainer/cgroups/devices/devices_emulator_test.go b/libcontainer/cgroups/devices/devices_emulator_test.go
index 4dac242c..5dbcbdee 100644
--- a/libcontainer/cgroups/devices/devices_emulator_test.go
+++ b/libcontainer/cgroups/devices/devices_emulator_test.go
@@ -19,10 +19,8 @@
 package devices
 
 import (
-	"bufio"
 	"bytes"
 	"reflect"
-	"strings"
 	"testing"
 
 	"github.com/opencontainers/runc/libcontainer/devices"
@@ -1114,31 +1112,3 @@ func TestDeviceEmulatorTransitionFromBlacklist(t *testing.T) {
 func TestDeviceEmulatorTransitionFromWhitelist(t *testing.T) {
 	testDeviceEmulatorTransition(t, false)
 }
-
-func BenchmarkParseLine(b *testing.B) {
-	list := `c *:* m
-b *:* m
-c 1:3 rwm
-c 1:5 rwm
-c 1:7 rwm
-c 1:8 rwm
-c 1:9 rwm
-c 5:0 rwm
-c 5:2 rwm
-c 136:* rwm
-c 10:200 rwm`
-
-	var r *deviceRule
-	var err error
-	for i := 0; i < b.N; i++ {
-		s := bufio.NewScanner(strings.NewReader(list))
-		for s.Scan() {
-			line := s.Text()
-			r, err = parseLine(line)
-		}
-		if err := s.Err(); err != nil {
-			b.Fatal(err)
-		}
-	}
-	b.Logf("rule: %v, err: %v", r, err)
-}
diff --git a/libcontainer/cgroups/file_test.go b/libcontainer/cgroups/file_test.go
index dc2b0630..4b2cb895 100644
--- a/libcontainer/cgroups/file_test.go
+++ b/libcontainer/cgroups/file_test.go
@@ -1,7 +1,8 @@
+// +build linux
+
 package cgroups
 
 import (
-	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -39,35 +40,3 @@ func TestWriteCgroupFileHandlesInterrupt(t *testing.T) {
 		}
 	}
 }
-
-func TestOpenat2(t *testing.T) {
-	if !IsCgroup2UnifiedMode() {
-		// The reason is many test cases below test opening files from
-		// the top-level directory, where cgroup v1 has no files.
-		t.Skip("test requires cgroup v2")
-	}
-
-	// Make sure we test openat2, not its fallback.
-	openFallback = func(_ string, _ int, _ os.FileMode) (*os.File, error) {
-		return nil, errors.New("fallback")
-	}
-	defer func() { openFallback = openAndCheck }()
-
-	for _, tc := range []struct{ dir, file string }{
-		{"/sys/fs/cgroup", "cgroup.controllers"},
-		{"/sys/fs/cgroup", "/cgroup.controllers"},
-		{"/sys/fs/cgroup/", "cgroup.controllers"},
-		{"/sys/fs/cgroup/", "/cgroup.controllers"},
-		{"/sys/fs/cgroup/user.slice", "cgroup.controllers"},
-		{"/sys/fs/cgroup/user.slice/", "/cgroup.controllers"},
-		{"/", "/sys/fs/cgroup/cgroup.controllers"},
-		{"/", "sys/fs/cgroup/cgroup.controllers"},
-		{"/sys/fs/cgroup/cgroup.controllers", ""},
-	} {
-		fd, err := OpenFile(tc.dir, tc.file, os.O_RDONLY)
-		if err != nil {
-			t.Errorf("case %+v: %v", tc, err)
-		}
-		fd.Close()
-	}
-}
diff --git a/libcontainer/cgroups/fs/blkio_test.go b/libcontainer/cgroups/fs/blkio_test.go
index eb6257c0..860628c7 100644
--- a/libcontainer/cgroups/fs/blkio_test.go
+++ b/libcontainer/cgroups/fs/blkio_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/cpu_test.go b/libcontainer/cgroups/fs/cpu_test.go
index 958e6040..77d12af8 100644
--- a/libcontainer/cgroups/fs/cpu_test.go
+++ b/libcontainer/cgroups/fs/cpu_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/cpuacct_test.go b/libcontainer/cgroups/fs/cpuacct_test.go
index 753a0194..aaae0aa4 100644
--- a/libcontainer/cgroups/fs/cpuacct_test.go
+++ b/libcontainer/cgroups/fs/cpuacct_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/cpuset_test.go b/libcontainer/cgroups/fs/cpuset_test.go
index 4918aaa2..17de7bfa 100644
--- a/libcontainer/cgroups/fs/cpuset_test.go
+++ b/libcontainer/cgroups/fs/cpuset_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/devices_test.go b/libcontainer/cgroups/fs/devices_test.go
index b2d1624c..a091543e 100644
--- a/libcontainer/cgroups/fs/devices_test.go
+++ b/libcontainer/cgroups/fs/devices_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/freezer_test.go b/libcontainer/cgroups/fs/freezer_test.go
index b441bf74..4f958497 100644
--- a/libcontainer/cgroups/fs/freezer_test.go
+++ b/libcontainer/cgroups/fs/freezer_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/fs_test.go b/libcontainer/cgroups/fs/fs_test.go
index b4430603..1d16ca5e 100644
--- a/libcontainer/cgroups/fs/fs_test.go
+++ b/libcontainer/cgroups/fs/fs_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/hugetlb_test.go b/libcontainer/cgroups/fs/hugetlb_test.go
index ac4ec153..33c8b6d2 100644
--- a/libcontainer/cgroups/fs/hugetlb_test.go
+++ b/libcontainer/cgroups/fs/hugetlb_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/memory_test.go b/libcontainer/cgroups/fs/memory_test.go
index 7d13d250..82adafc5 100644
--- a/libcontainer/cgroups/fs/memory_test.go
+++ b/libcontainer/cgroups/fs/memory_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/net_cls_test.go b/libcontainer/cgroups/fs/net_cls_test.go
index 6c36f055..7c0ba03d 100644
--- a/libcontainer/cgroups/fs/net_cls_test.go
+++ b/libcontainer/cgroups/fs/net_cls_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/net_prio_test.go b/libcontainer/cgroups/fs/net_prio_test.go
index 7983041a..a1bb2ce4 100644
--- a/libcontainer/cgroups/fs/net_prio_test.go
+++ b/libcontainer/cgroups/fs/net_prio_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/pids_test.go b/libcontainer/cgroups/fs/pids_test.go
index d05ec721..50777716 100644
--- a/libcontainer/cgroups/fs/pids_test.go
+++ b/libcontainer/cgroups/fs/pids_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/stats_util_test.go b/libcontainer/cgroups/fs/stats_util_test.go
index 9b7d840a..757e65b6 100644
--- a/libcontainer/cgroups/fs/stats_util_test.go
+++ b/libcontainer/cgroups/fs/stats_util_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fs
 
 import (
diff --git a/libcontainer/cgroups/fs/util_test.go b/libcontainer/cgroups/fs/util_test.go
index 4a401377..7b7858f6 100644
--- a/libcontainer/cgroups/fs/util_test.go
+++ b/libcontainer/cgroups/fs/util_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 /*
 Utility for testing cgroup operations.
 
diff --git a/libcontainer/cgroups/fscommon/rdma_test.go b/libcontainer/cgroups/fscommon/rdma_test.go
deleted file mode 100644
index 9bc08190..00000000
--- a/libcontainer/cgroups/fscommon/rdma_test.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package fscommon
-
-import (
-	"os"
-	"path/filepath"
-	"testing"
-
-	"github.com/opencontainers/runc/libcontainer/configs"
-)
-
-/* Roadmap for future */
-// (Low-priority)  TODO: Check if it is possible to virtually mimic an actual RDMA device.
-// TODO: Think of more edge-cases to add.
-
-// TestRdmaSet performs an E2E test of RdmaSet(), parseRdmaKV() using dummy device and a dummy cgroup file-system.
-// Note: Following test does not guarantees that your host supports RDMA since this mocks underlying infrastructure.
-func TestRdmaSet(t *testing.T) {
-	testCgroupPath := filepath.Join(t.TempDir(), "rdma")
-
-	// Ensure the full mock cgroup path exists.
-	err := os.Mkdir(testCgroupPath, 0o755)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	rdmaDevice := "mlx5_1"
-	maxHandles := uint32(100)
-	maxObjects := uint32(300)
-
-	rdmaStubResource := &configs.Resources{
-		Rdma: map[string]configs.LinuxRdma{
-			rdmaDevice: {
-				HcaHandles: &maxHandles,
-				HcaObjects: &maxObjects,
-			},
-		},
-	}
-
-	if err := RdmaSet(testCgroupPath, rdmaStubResource); err != nil {
-		t.Fatal(err)
-	}
-
-	// The default rdma.max must be written.
-	rdmaEntries, err := readRdmaEntries(testCgroupPath, "rdma.max")
-	if err != nil {
-		t.Fatal(err)
-	}
-	if len(rdmaEntries) != 1 {
-		t.Fatal("rdma_test: Got the wrong values while parsing entries from rdma.max")
-	}
-	if rdmaEntries[0].HcaHandles != maxHandles {
-		t.Fatalf("rdma_test: Got the wrong value for hca_handles")
-	}
-	if rdmaEntries[0].HcaObjects != maxObjects {
-		t.Fatalf("rdma_test: Got the wrong value for hca_Objects")
-	}
-}
diff --git a/libcontainer/cgroups/fscommon/utils_test.go b/libcontainer/cgroups/fscommon/utils_test.go
index 6470a47a..d429db93 100644
--- a/libcontainer/cgroups/fscommon/utils_test.go
+++ b/libcontainer/cgroups/fscommon/utils_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package fscommon
 
 import (
diff --git a/libcontainer/cgroups/getallpids_test.go b/libcontainer/cgroups/getallpids_test.go
index e6b06323..b6100686 100644
--- a/libcontainer/cgroups/getallpids_test.go
+++ b/libcontainer/cgroups/getallpids_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package cgroups
 
 import (
diff --git a/libcontainer/cgroups/utils_test.go b/libcontainer/cgroups/utils_test.go
index 929cf511..816bdc83 100644
--- a/libcontainer/cgroups/utils_test.go
+++ b/libcontainer/cgroups/utils_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package cgroups
 
 import (
diff --git a/libcontainer/container_linux_test.go b/libcontainer/container_linux_test.go
index 3eb6e5af..01b167c5 100644
--- a/libcontainer/container_linux_test.go
+++ b/libcontainer/container_linux_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/devices/device_unix_test.go b/libcontainer/devices/device_unix_test.go
index 9a93359f..118c3bfa 100644
--- a/libcontainer/devices/device_unix_test.go
+++ b/libcontainer/devices/device_unix_test.go
@@ -1,4 +1,3 @@
-//go:build !windows
 // +build !windows
 
 package devices
diff --git a/libcontainer/factory_linux_test.go b/libcontainer/factory_linux_test.go
index fe34473c..5d389909 100644
--- a/libcontainer/factory_linux_test.go
+++ b/libcontainer/factory_linux_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/integration/seccomp_test.go b/libcontainer/integration/seccomp_test.go
index ea8ee419..b2541306 100644
--- a/libcontainer/integration/seccomp_test.go
+++ b/libcontainer/integration/seccomp_test.go
@@ -1,4 +1,3 @@
-//go:build linux && cgo && seccomp
 // +build linux,cgo,seccomp
 
 package integration
diff --git a/libcontainer/intelrdt/intelrdt_test.go b/libcontainer/intelrdt/intelrdt_test.go
index 09ced529..31df5186 100644
--- a/libcontainer/intelrdt/intelrdt_test.go
+++ b/libcontainer/intelrdt/intelrdt_test.go
@@ -1,15 +1,19 @@
+// +build linux
+
 package intelrdt
 
 import (
 	"errors"
 	"io"
-	"os"
-	"path/filepath"
 	"strings"
 	"testing"
 )
 
 func TestIntelRdtSetL3CacheSchema(t *testing.T) {
+	if !IsCATEnabled() {
+		return
+	}
+
 	helper := NewIntelRdtTestUtil(t)
 
 	const (
@@ -40,6 +44,10 @@ func TestIntelRdtSetL3CacheSchema(t *testing.T) {
 }
 
 func TestIntelRdtSetMemBwSchema(t *testing.T) {
+	if !IsMBAEnabled() {
+		return
+	}
+
 	helper := NewIntelRdtTestUtil(t)
 
 	const (
@@ -70,6 +78,10 @@ func TestIntelRdtSetMemBwSchema(t *testing.T) {
 }
 
 func TestIntelRdtSetMemBwScSchema(t *testing.T) {
+	if !IsMBAScEnabled() {
+		return
+	}
+
 	helper := NewIntelRdtTestUtil(t)
 
 	const (
@@ -99,35 +111,6 @@ func TestIntelRdtSetMemBwScSchema(t *testing.T) {
 	}
 }
 
-func TestApply(t *testing.T) {
-	helper := NewIntelRdtTestUtil(t)
-
-	const closID = "test-clos"
-
-	helper.IntelRdtData.config.IntelRdt.ClosID = closID
-	intelrdt := NewManager(helper.IntelRdtData.config, "", helper.IntelRdtPath)
-	if err := intelrdt.Apply(1234); err == nil {
-		t.Fatal("unexpected success when applying pid")
-	}
-	if _, err := os.Stat(filepath.Join(helper.IntelRdtPath, closID)); err == nil {
-		t.Fatal("closid dir should not exist")
-	}
-
-	// Dir should be created if some schema has been specified
-	intelrdt.(*intelRdtManager).config.IntelRdt.L3CacheSchema = "L3:0=f"
-	if err := intelrdt.Apply(1235); err != nil {
-		t.Fatalf("Apply() failed: %v", err)
-	}
-
-	pids, err := getIntelRdtParamString(intelrdt.GetPath(), "tasks")
-	if err != nil {
-		t.Fatalf("failed to read tasks file: %v", err)
-	}
-	if pids != "1235" {
-		t.Fatalf("unexpected tasks file, expected '1235', got %q", pids)
-	}
-}
-
 const (
 	mountinfoValid = `18 40 0:18 / /sys rw,nosuid,nodev,noexec,relatime shared:6 - sysfs sysfs rw
 ￼19 40 0:3 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
diff --git a/libcontainer/intelrdt/mbm_test.go b/libcontainer/intelrdt/mbm_test.go
index 4f22cbd0..319f8de2 100644
--- a/libcontainer/intelrdt/mbm_test.go
+++ b/libcontainer/intelrdt/mbm_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package intelrdt
 
 import (
diff --git a/libcontainer/intelrdt/util_test.go b/libcontainer/intelrdt/util_test.go
index a5fcc97b..ced12779 100644
--- a/libcontainer/intelrdt/util_test.go
+++ b/libcontainer/intelrdt/util_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 /*
  * Utility for testing Intel RDT operations.
  * Creates a mock of the Intel RDT "resource control" filesystem for the duration of the test.
@@ -28,9 +30,9 @@ func NewIntelRdtTestUtil(t *testing.T) *intelRdtTestUtil {
 		config: &configs.Config{
 			IntelRdt: &configs.IntelRdt{},
 		},
+		root: t.TempDir(),
 	}
-	intelRdtRoot = t.TempDir()
-	testIntelRdtPath := filepath.Join(intelRdtRoot, "resctrl")
+	testIntelRdtPath := filepath.Join(d.root, "resctrl")
 
 	// Ensure the full mock Intel RDT "resource control" filesystem path exists
 	if err := os.MkdirAll(testIntelRdtPath, 0o755); err != nil {
diff --git a/libcontainer/logs/logs_linux_test.go b/libcontainer/logs/logs_linux_test.go
index 7e2b59d9..d6563fd2 100644
--- a/libcontainer/logs/logs_linux_test.go
+++ b/libcontainer/logs/logs_linux_test.go
@@ -11,44 +11,51 @@ import (
 	"github.com/sirupsen/logrus"
 )
 
+const msgErr = `"level":"error"`
+
 func TestLoggingToFile(t *testing.T) {
 	l := runLogForwarding(t)
 
-	logToLogWriter(t, l, `{"level": "info","msg":"kitten"}`)
+	msg := `"level":"info","msg":"kitten"`
+	logToLogWriter(t, l, msg)
 	finish(t, l)
-	check(t, l, "kitten")
+	check(t, l, msg, msgErr)
 }
 
 func TestLogForwardingDoesNotStopOnJsonDecodeErr(t *testing.T) {
 	l := runLogForwarding(t)
 
-	logToLogWriter(t, l, "invalid-json-with-kitten")
-	checkWait(t, l, "failed to decode")
+	logToLogWriter(t, l, `"invalid-json-with-kitten"`)
+	checkWait(t, l, msgErr, "")
 
 	truncateLogFile(t, l.file)
 
-	logToLogWriter(t, l, `{"level": "info","msg":"puppy"}`)
+	msg := `"level":"info","msg":"puppy"`
+	logToLogWriter(t, l, msg)
 	finish(t, l)
-	check(t, l, "puppy")
+	check(t, l, msg, msgErr)
 }
 
 func TestLogForwardingDoesNotStopOnLogLevelParsingErr(t *testing.T) {
 	l := runLogForwarding(t)
 
-	logToLogWriter(t, l, `{"level": "alert","msg":"puppy"}`)
-	checkWait(t, l, "failed to parse log level")
+	msg := `"level":"alert","msg":"puppy"`
+	logToLogWriter(t, l, msg)
+	checkWait(t, l, msgErr, msg)
 
 	truncateLogFile(t, l.file)
 
-	logToLogWriter(t, l, `{"level": "info","msg":"puppy"}`)
+	msg = `"level":"info","msg":"puppy"`
+	logToLogWriter(t, l, msg)
 	finish(t, l)
-	check(t, l, "puppy")
+	check(t, l, msg, msgErr)
 }
 
 func TestLogForwardingStopsAfterClosingTheWriter(t *testing.T) {
 	l := runLogForwarding(t)
 
-	logToLogWriter(t, l, `{"level": "info","msg":"sync"}`)
+	msg := `"level":"info","msg":"sync"`
+	logToLogWriter(t, l, msg)
 
 	// Do not use finish() here as we check done pipe ourselves.
 	l.w.Close()
@@ -58,12 +65,12 @@ func TestLogForwardingStopsAfterClosingTheWriter(t *testing.T) {
 		t.Fatal("log forwarding did not stop after closing the pipe")
 	}
 
-	check(t, l, "sync")
+	check(t, l, msg, msgErr)
 }
 
 func logToLogWriter(t *testing.T, l *log, message string) {
 	t.Helper()
-	_, err := l.w.Write([]byte(message + "\n"))
+	_, err := l.w.Write([]byte("{" + message + "}\n"))
 	if err != nil {
 		t.Fatalf("failed to write %q to log writer: %v", message, err)
 	}
@@ -71,7 +78,7 @@ func logToLogWriter(t *testing.T, l *log, message string) {
 
 type log struct {
 	w    io.WriteCloser
-	file string
+	file *os.File
 	done chan error
 }
 
@@ -90,19 +97,16 @@ func runLogForwarding(t *testing.T) *log {
 	if err != nil {
 		t.Fatal(err)
 	}
-	tempFile.Close()
-	logFile := tempFile.Name()
-	t.Cleanup(func() { os.Remove(logFile) })
-
-	logConfig := Config{LogLevel: logrus.InfoLevel, LogFormat: "json", LogFilePath: logFile}
-	loggingConfigured = false
-	if err := ConfigureLogging(logConfig); err != nil {
-		t.Fatal(err)
-	}
+	t.Cleanup(func() {
+		tempFile.Close()
+		os.Remove(tempFile.Name())
+	})
 
+	logrus.SetOutput(tempFile)
+	logrus.SetFormatter(&logrus.JSONFormatter{})
 	doneForwarding := ForwardLogs(logR)
 
-	return &log{w: logW, done: doneForwarding, file: logFile}
+	return &log{w: logW, done: doneForwarding, file: tempFile}
 }
 
 func finish(t *testing.T, l *log) {
@@ -113,43 +117,40 @@ func finish(t *testing.T, l *log) {
 	}
 }
 
-func truncateLogFile(t *testing.T, logFile string) {
+func truncateLogFile(t *testing.T, file *os.File) {
 	t.Helper()
-	file, err := os.OpenFile(logFile, os.O_RDWR, 0o600)
-	if err != nil {
-		t.Fatalf("failed to open log file: %v", err)
-		return
-	}
-	defer file.Close()
 
-	err = file.Truncate(0)
+	err := file.Truncate(0)
 	if err != nil {
 		t.Fatalf("failed to truncate log file: %v", err)
 	}
 }
 
-// check checks that file contains txt
-func check(t *testing.T, l *log, txt string) {
+// check checks that the file contains txt and does not contain notxt.
+func check(t *testing.T, l *log, txt, notxt string) {
 	t.Helper()
-	contents, err := ioutil.ReadFile(l.file)
+	contents, err := ioutil.ReadFile(l.file.Name())
 	if err != nil {
 		t.Fatal(err)
 	}
-	if !bytes.Contains(contents, []byte(txt)) {
-		t.Fatalf("%q does not contain %q", string(contents), txt)
+	if txt != "" && !bytes.Contains(contents, []byte(txt)) {
+		t.Fatalf("%s does not contain %s", contents, txt)
+	}
+	if notxt != "" && bytes.Contains(contents, []byte(notxt)) {
+		t.Fatalf("%s does contain %s", contents, notxt)
 	}
 }
 
-// checkWait checks that file contains txt. If the file is empty,
+// checkWait is like check, but if the file is empty,
 // it waits until it's not.
-func checkWait(t *testing.T, l *log, txt string) {
+func checkWait(t *testing.T, l *log, txt string, notxt string) {
 	t.Helper()
 	const (
 		delay = 100 * time.Millisecond
 		iter  = 3
 	)
 	for i := 0; ; i++ {
-		st, err := os.Stat(l.file)
+		st, err := l.file.Stat()
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -157,10 +158,10 @@ func checkWait(t *testing.T, l *log, txt string) {
 			break
 		}
 		if i == iter {
-			t.Fatalf("waited %s for file %s to be non-empty but it still is", iter*delay, l.file)
+			t.Fatalf("waited %s for file %s to be non-empty but it still is", iter*delay, l.file.Name())
 		}
 		time.Sleep(delay)
 	}
 
-	check(t, l, txt)
+	check(t, l, txt, notxt)
 }
diff --git a/libcontainer/notify_linux_test.go b/libcontainer/notify_linux_test.go
index cf0f90f4..80859c61 100644
--- a/libcontainer/notify_linux_test.go
+++ b/libcontainer/notify_linux_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/nsenter/nsenter_test.go b/libcontainer/nsenter/nsenter_test.go
index 0cbf0aae..9201f026 100644
--- a/libcontainer/nsenter/nsenter_test.go
+++ b/libcontainer/nsenter/nsenter_test.go
@@ -6,6 +6,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"os"
 	"os/exec"
 	"strings"
@@ -16,9 +17,21 @@ import (
 	"golang.org/x/sys/unix"
 )
 
+type pid struct {
+	Pid int `json:"Pid"`
+}
+
+type logentry struct {
+	Msg   string `json:"msg"`
+	Level string `json:"level"`
+}
+
 func TestNsenterValidPaths(t *testing.T) {
 	args := []string{"nsenter-exec"}
-	parent, child := newPipe(t)
+	parent, child, err := newPipe()
+	if err != nil {
+		t.Fatalf("failed to create pipe %v", err)
+	}
 
 	namespaces := []string{
 		// join pid ns of the current process
@@ -34,9 +47,8 @@ func TestNsenterValidPaths(t *testing.T) {
 	}
 
 	if err := cmd.Start(); err != nil {
-		t.Fatalf("nsenter failed to start: %v", err)
+		t.Fatalf("nsenter failed to start %v", err)
 	}
-	child.Close()
 
 	// write cloneFlags
 	r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
@@ -54,18 +66,36 @@ func TestNsenterValidPaths(t *testing.T) {
 
 	initWaiter(t, parent)
 
+	decoder := json.NewDecoder(parent)
+	var pid *pid
+
 	if err := cmd.Wait(); err != nil {
-		t.Fatalf("nsenter error: %v", err)
+		t.Fatalf("nsenter exits with a non-zero exit status")
+	}
+	if err := decoder.Decode(&pid); err != nil {
+		dir, _ := ioutil.ReadDir(fmt.Sprintf("/proc/%d/ns", os.Getpid()))
+		for _, d := range dir {
+			t.Log(d.Name())
+		}
+		t.Fatalf("%v", err)
 	}
 
-	reapChildren(t, parent)
+	p, err := os.FindProcess(pid.Pid)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+	_, _ = p.Wait()
 }
 
 func TestNsenterInvalidPaths(t *testing.T) {
 	args := []string{"nsenter-exec"}
-	parent, child := newPipe(t)
+	parent, child, err := newPipe()
+	if err != nil {
+		t.Fatalf("failed to create pipe %v", err)
+	}
 
 	namespaces := []string{
+		// join pid ns of the current process
 		fmt.Sprintf("pid:/proc/%d/ns/pid", -1),
 	}
 	cmd := &exec.Cmd{
@@ -76,10 +106,8 @@ func TestNsenterInvalidPaths(t *testing.T) {
 	}
 
 	if err := cmd.Start(); err != nil {
-		t.Fatalf("nsenter failed to start: %v", err)
+		t.Fatal(err)
 	}
-	child.Close()
-
 	// write cloneFlags
 	r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
 	r.AddData(&libcontainer.Int32msg{
@@ -102,9 +130,13 @@ func TestNsenterInvalidPaths(t *testing.T) {
 
 func TestNsenterIncorrectPathType(t *testing.T) {
 	args := []string{"nsenter-exec"}
-	parent, child := newPipe(t)
+	parent, child, err := newPipe()
+	if err != nil {
+		t.Fatalf("failed to create pipe %v", err)
+	}
 
 	namespaces := []string{
+		// join pid ns of the current process
 		fmt.Sprintf("net:/proc/%d/ns/pid", os.Getpid()),
 	}
 	cmd := &exec.Cmd{
@@ -115,10 +147,8 @@ func TestNsenterIncorrectPathType(t *testing.T) {
 	}
 
 	if err := cmd.Start(); err != nil {
-		t.Fatalf("nsenter failed to start: %v", err)
+		t.Fatal(err)
 	}
-	child.Close()
-
 	// write cloneFlags
 	r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
 	r.AddData(&libcontainer.Int32msg{
@@ -135,14 +165,24 @@ func TestNsenterIncorrectPathType(t *testing.T) {
 
 	initWaiter(t, parent)
 	if err := cmd.Wait(); err == nil {
-		t.Fatalf("nsenter error: %v", err)
+		t.Fatalf("nsenter exits with a zero exit status")
 	}
 }
 
 func TestNsenterChildLogging(t *testing.T) {
 	args := []string{"nsenter-exec"}
-	parent, child := newPipe(t)
-	logread, logwrite := newPipe(t)
+	parent, child, err := newPipe()
+	if err != nil {
+		t.Fatalf("failed to create exec pipe %v", err)
+	}
+	logread, logwrite, err := os.Pipe()
+	if err != nil {
+		t.Fatalf("failed to create log pipe %v", err)
+	}
+	defer func() {
+		_ = logwrite.Close()
+		_ = logread.Close()
+	}()
 
 	namespaces := []string{
 		// join pid ns of the current process
@@ -158,11 +198,8 @@ func TestNsenterChildLogging(t *testing.T) {
 	}
 
 	if err := cmd.Start(); err != nil {
-		t.Fatalf("nsenter failed to start: %v", err)
+		t.Fatalf("nsenter failed to start %v", err)
 	}
-	child.Close()
-	logwrite.Close()
-
 	// write cloneFlags
 	r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
 	r.AddData(&libcontainer.Int32msg{
@@ -179,12 +216,20 @@ func TestNsenterChildLogging(t *testing.T) {
 
 	initWaiter(t, parent)
 
-	getLogs(t, logread)
-	if err := cmd.Wait(); err != nil {
-		t.Fatalf("nsenter error: %v", err)
+	logsDecoder := json.NewDecoder(logread)
+	var logentry *logentry
+
+	err = logsDecoder.Decode(&logentry)
+	if err != nil {
+		t.Fatalf("child log: %v", err)
+	}
+	if logentry.Level == "" || logentry.Msg == "" {
+		t.Fatalf("child log: empty log fields: level=\"%s\" msg=\"%s\"", logentry.Level, logentry.Msg)
 	}
 
-	reapChildren(t, parent)
+	if err := cmd.Wait(); err != nil {
+		t.Fatalf("nsenter exits with a non-zero exit status")
+	}
 }
 
 func init() {
@@ -193,19 +238,12 @@ func init() {
 	}
 }
 
-func newPipe(t *testing.T) (parent *os.File, child *os.File) {
-	t.Helper()
+func newPipe() (parent *os.File, child *os.File, err error) {
 	fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
 	if err != nil {
-		t.Fatal("socketpair failed:", err)
+		return nil, nil, err
 	}
-	parent = os.NewFile(uintptr(fds[1]), "parent")
-	child = os.NewFile(uintptr(fds[0]), "child")
-	t.Cleanup(func() {
-		parent.Close()
-		child.Close()
-	})
-	return
+	return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
 }
 
 // initWaiter reads back the initial \0 from runc init
@@ -223,47 +261,3 @@ func initWaiter(t *testing.T, r io.Reader) {
 	}
 	t.Fatalf("waiting for init preliminary setup: %v", err)
 }
-
-func reapChildren(t *testing.T, parent *os.File) {
-	t.Helper()
-	decoder := json.NewDecoder(parent)
-	decoder.DisallowUnknownFields()
-	var pid struct {
-		Pid2 int `json:"stage2_pid"`
-		Pid1 int `json:"stage1_pid"`
-	}
-	if err := decoder.Decode(&pid); err != nil {
-		t.Fatal(err)
-	}
-
-	// Reap children.
-	_, _ = unix.Wait4(pid.Pid1, nil, 0, nil)
-	_, _ = unix.Wait4(pid.Pid2, nil, 0, nil)
-
-	// Sanity check.
-	if pid.Pid1 == 0 || pid.Pid2 == 0 {
-		t.Fatal("got pids:", pid)
-	}
-}
-
-func getLogs(t *testing.T, logread *os.File) {
-	logsDecoder := json.NewDecoder(logread)
-	logsDecoder.DisallowUnknownFields()
-	var logentry struct {
-		Level string `json:"level"`
-		Msg   string `json:"msg"`
-	}
-
-	for {
-		if err := logsDecoder.Decode(&logentry); err != nil {
-			if errors.Is(err, io.EOF) {
-				return
-			}
-			t.Fatal("init log decoding error:", err)
-		}
-		t.Logf("logentry: %+v", logentry)
-		if logentry.Level == "" || logentry.Msg == "" {
-			t.Fatalf("init log: empty log entry: %+v", logentry)
-		}
-	}
-}
diff --git a/libcontainer/rootfs_linux_test.go b/libcontainer/rootfs_linux_test.go
index e3bfdc50..1bfe7c66 100644
--- a/libcontainer/rootfs_linux_test.go
+++ b/libcontainer/rootfs_linux_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/seccomp/patchbpf/enosys_linux_test.go b/libcontainer/seccomp/patchbpf/enosys_linux_test.go
index b2ee6255..ce5f0254 100644
--- a/libcontainer/seccomp/patchbpf/enosys_linux_test.go
+++ b/libcontainer/seccomp/patchbpf/enosys_linux_test.go
@@ -1,5 +1,4 @@
-//go:build cgo && seccomp
-// +build cgo,seccomp
+// +build linux,cgo,seccomp
 
 package patchbpf
 
diff --git a/libcontainer/specconv/spec_linux_test.go b/libcontainer/specconv/spec_linux_test.go
index a81090be..316a21e8 100644
--- a/libcontainer/specconv/spec_linux_test.go
+++ b/libcontainer/specconv/spec_linux_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package specconv
 
 import (
diff --git a/libcontainer/state_linux_test.go b/libcontainer/state_linux_test.go
index 413626d5..a4558a5b 100644
--- a/libcontainer/state_linux_test.go
+++ b/libcontainer/state_linux_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package libcontainer
 
 import (
diff --git a/libcontainer/userns/userns_linux_test.go b/libcontainer/userns/userns_linux_test.go
index 90d9270d..2ddd3ff9 100644
--- a/libcontainer/userns/userns_linux_test.go
+++ b/libcontainer/userns/userns_linux_test.go
@@ -1,3 +1,5 @@
+// +build linux
+
 package userns
 
 import (
diff --git a/tests/integration/helpers.bash b/tests/integration/helpers.bash
index fde767b1..fe3a324b 100644
--- a/tests/integration/helpers.bash
+++ b/tests/integration/helpers.bash
@@ -7,7 +7,7 @@ if [ -z "$BATS_RUN_TMPDIR" ]; then
 fi
 
 # Root directory of integration tests.
-INTEGRATION_ROOT=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")
+INTEGRATION_ROOT=$(dirname "$(readlink -f "$BASH_SOURCE")")
 
 # Download images, get *_IMAGE variables.
 IMAGES=$("${INTEGRATION_ROOT}"/get-images.sh)
@@ -16,10 +16,8 @@ unset IMAGES
 
 RUNC="${INTEGRATION_ROOT}/../../runc"
 RECVTTY="${INTEGRATION_ROOT}/../../contrib/cmd/recvtty/recvtty"
-SD_HELPER="${INTEGRATION_ROOT}/../../contrib/cmd/sd-helper/sd-helper"
 
 # Test data path.
-# shellcheck disable=SC2034
 TESTDATA="${INTEGRATION_ROOT}/testdata"
 
 # CRIU PATH
@@ -40,9 +38,7 @@ function runc() {
 
 	# Some debug information to make life easier. bats will only print it if the
 	# test failed, in which case the output is useful.
-	# shellcheck disable=SC2154
-	echo "runc $* (status=$status):" >&2
-	# shellcheck disable=SC2154
+	echo "runc $@ (status=$status):" >&2
 	echo "$output" >&2
 }
 
@@ -51,34 +47,44 @@ function __runc() {
 	"$RUNC" ${RUNC_USE_SYSTEMD+--systemd-cgroup} --root "$ROOT/state" "$@"
 }
 
-# Wrapper for runc spec.
+# Wrapper for runc spec, which takes only one argument (the bundle path).
 function runc_spec() {
+	! [[ "$#" > 1 ]]
+
 	local args=()
+	local bundle=""
+
 	if [ "$ROOTLESS" -ne 0 ]; then
 		args+=("--rootless")
 	fi
+	if [ "$#" -ne 0 ]; then
+		bundle="$1"
+		args+=("--bundle" "$bundle")
+	fi
 
 	runc spec "${args[@]}"
 
 	# Always add additional mappings if we have idmaps.
 	if [[ "$ROOTLESS" -ne 0 ]] && [[ "$ROOTLESS_FEATURES" == *"idmap"* ]]; then
-		runc_rootless_idmap
+		runc_rootless_idmap "$bundle"
 	fi
 }
 
 # Helper function to reformat config.json file. Input uses jq syntax.
 function update_config() {
-	jq "$1" "./config.json" | awk 'BEGIN{RS="";getline<"-";print>ARGV[1]}' "./config.json"
+	bundle="${2:-.}"
+	jq "$1" "$bundle/config.json" | awk 'BEGIN{RS="";getline<"-";print>ARGV[1]}' "$bundle/config.json"
 }
 
 # Shortcut to add additional uids and gids, based on the values set as part of
 # a rootless configuration.
 function runc_rootless_idmap() {
+	bundle="${1:-.}"
 	update_config ' .mounts |= map((select(.type == "devpts") | .options += ["gid=5"]) // .)
 			| .linux.uidMappings += [{"hostID": '"$ROOTLESS_UIDMAP_START"', "containerID": 1000, "size": '"$ROOTLESS_UIDMAP_LENGTH"'}]
 			| .linux.gidMappings += [{"hostID": '"$ROOTLESS_GIDMAP_START"', "containerID": 100, "size": 1}]
-			| .linux.gidMappings += [{"hostID": '"$((ROOTLESS_GIDMAP_START + 10))"', "containerID": 1, "size": 20}]
-			| .linux.gidMappings += [{"hostID": '"$((ROOTLESS_GIDMAP_START + 100))"', "containerID": 1000, "size": '"$((ROOTLESS_GIDMAP_LENGTH - 1000))"'}]'
+			| .linux.gidMappings += [{"hostID": '"$(($ROOTLESS_GIDMAP_START + 10))"', "containerID": 1, "size": 20}]
+			| .linux.gidMappings += [{"hostID": '"$(($ROOTLESS_GIDMAP_START + 100))"', "containerID": 1000, "size": '"$(($ROOTLESS_GIDMAP_LENGTH - 1000))"'}]' $bundle
 }
 
 # Returns systemd version as a number (-1 if systemd is not enabled/supported).
@@ -125,92 +131,32 @@ function init_cgroup_paths() {
 		CGROUP_SUBSYSTEMS=$(awk '!/^#/ {print $1}' /proc/cgroups)
 		local g base_path
 		for g in ${CGROUP_SUBSYSTEMS}; do
-			base_path=$(gawk '$(NF-2) == "cgroup" && $NF ~ /\<'"${g}"'\>/ { print $5; exit }' /proc/self/mountinfo)
+			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="${base_path}"
 		done
 	fi
 }
 
-function create_parent() {
-	if [ -n "$RUNC_USE_SYSTEMD" ]; then
-		[ -z "$SD_PARENT_NAME" ] && return
-		"$SD_HELPER" --parent machine.slice start "$SD_PARENT_NAME"
-	else
-		[ -z "$REL_PARENT_PATH" ] && return
-		if [ "$CGROUP_UNIFIED" == "yes" ]; then
-			mkdir "/sys/fs/cgroup$REL_PARENT_PATH"
-		else
-			local subsys
-			for subsys in ${CGROUP_SUBSYSTEMS}; do
-				# Have to ignore EEXIST (-p) as some subsystems
-				# are mounted together (e.g. cpu,cpuacct), so
-				# the path is created more than once.
-				mkdir -p "/sys/fs/cgroup/$subsys$REL_PARENT_PATH"
-			done
-		fi
-	fi
-}
-
-function remove_parent() {
-	if [ -n "$RUNC_USE_SYSTEMD" ]; then
-		[ -z "$SD_PARENT_NAME" ] && return
-		"$SD_HELPER" --parent machine.slice stop "$SD_PARENT_NAME"
-	else
-		[ -z "$REL_PARENT_PATH" ] && return
-		if [ "$CGROUP_UNIFIED" == "yes" ]; then
-			rmdir "/sys/fs/cgroup/$REL_PARENT_PATH"
-		else
-			local subsys
-			for subsys in ${CGROUP_SUBSYSTEMS} systemd; do
-				rmdir "/sys/fs/cgroup/$subsys/$REL_PARENT_PATH"
-			done
-		fi
-	fi
-	unset SD_PARENT_NAME
-	unset REL_PARENT_PATH
-}
-
-function set_parent_systemd_properties() {
-	[ -z "$SD_PARENT_NAME" ] && return
-	local user
-	[ "$(id -u)" != "0" ] && user="--user"
-	systemctl set-property $user "$SD_PARENT_NAME" "$@"
-}
-
 # Randomize cgroup path(s), and update cgroupsPath in config.json.
 # This function sets a few cgroup-related variables.
-#
-# Optional parameter $1 is a pod/parent name. If set, a parent/pod cgroup is
-# created, and variables $REL_PARENT_PATH and $SD_PARENT_NAME can be used to
-# refer to it.
 function set_cgroups_path() {
+	bundle="${1:-.}"
 	init_cgroup_paths
-	local pod dash_pod slash_pod pod_slice
-	if [ "$#" -ne 0 ] && [ "$1" != "" ]; then
-		# Set up a parent/pod cgroup.
-		pod="$1"
-		dash_pod="-$pod"
-		slash_pod="/$pod"
-		SD_PARENT_NAME="machine-${pod}.slice"
-		pod_slice="/$SD_PARENT_NAME"
-	fi
 
 	local rnd="$RANDOM"
 	if [ -n "${RUNC_USE_SYSTEMD}" ]; then
 		SD_UNIT_NAME="runc-cgroups-integration-test-${rnd}.scope"
 		if [ "$(id -u)" = "0" ]; then
-			REL_PARENT_PATH="/machine.slice${pod_slice}"
-			OCI_CGROUPS_PATH="machine${dash_pod}.slice:runc-cgroups:integration-test-${rnd}"
+			REL_CGROUPS_PATH="/machine.slice/$SD_UNIT_NAME"
+			OCI_CGROUPS_PATH="machine.slice:runc-cgroups:integration-test-${rnd}"
 		else
-			REL_PARENT_PATH="/user.slice/user-$(id -u).slice/user@$(id -u).service/machine.slice${pod_slice}"
+			REL_CGROUPS_PATH="/user.slice/user-$(id -u).slice/user@$(id -u).service/machine.slice/$SD_UNIT_NAME"
 			# OCI path doesn't contain "/user.slice/user-$(id -u).slice/user@$(id -u).service/" prefix
-			OCI_CGROUPS_PATH="machine${dash_pod}.slice:runc-cgroups:integration-test-${rnd}"
+			OCI_CGROUPS_PATH="machine.slice:runc-cgroups:integration-test-${rnd}"
 		fi
-		REL_CGROUPS_PATH="$REL_PARENT_PATH/$SD_UNIT_NAME"
 	else
-		REL_PARENT_PATH="/runc-cgroups-integration-test${slash_pod}"
-		REL_CGROUPS_PATH="$REL_PARENT_PATH/test-cgroup-${rnd}"
+		REL_CGROUPS_PATH="/runc-cgroups-integration-test/test-cgroup-${rnd}"
 		OCI_CGROUPS_PATH=$REL_CGROUPS_PATH
 	fi
 
@@ -219,9 +165,7 @@ function set_cgroups_path() {
 		CGROUP_PATH=${CGROUP_BASE_PATH}${REL_CGROUPS_PATH}
 	fi
 
-	[ -n "$pod" ] && create_parent
-
-	update_config '.linux.cgroupsPath |= "'"${OCI_CGROUPS_PATH}"'"'
+	update_config '.linux.cgroupsPath |= "'"${OCI_CGROUPS_PATH}"'"' "$bundle"
 }
 
 # Get a value from a cgroup file.
@@ -229,39 +173,39 @@ function get_cgroup_value() {
 	local source=$1
 	local cgroup var current
 
-	if [ "$CGROUP_UNIFIED" = "yes" ]; then
+	if [ "x$CGROUP_UNIFIED" = "xyes" ]; then
 		cgroup=$CGROUP_PATH
 	else
 		var=${source%%.*}             # controller name (e.g. memory)
 		var=CGROUP_${var^^}_BASE_PATH # variable name (e.g. CGROUP_MEMORY_BASE_PATH)
-		eval cgroup=\$"${var}${REL_CGROUPS_PATH}"
+		eval cgroup=\$${var}${REL_CGROUPS_PATH}
 	fi
-	cat "$cgroup/$source"
+	cat $cgroup/$source
 }
 
 # Helper to check a if value in a cgroup file matches the expected one.
 function check_cgroup_value() {
 	local current
-	current="$(get_cgroup_value "$1")"
+	current="$(get_cgroup_value $1)"
 	local expected=$2
 
-	echo "current $current !? $expected"
+	echo "current" $current "!?" "$expected"
 	[ "$current" = "$expected" ]
 }
 
 # Helper to check a value in systemd.
 function check_systemd_value() {
 	[ -z "${RUNC_USE_SYSTEMD}" ] && return
-	local source="$1"
+	local source=$1
 	[ "$source" = "unsupported" ] && return
 	local expected="$2"
 	local expected2="$3"
 	local user=""
-	[ "$(id -u)" != "0" ] && user="--user"
+	[ $(id -u) != "0" ] && user="--user"
 
-	current=$(systemctl show $user --property "$source" "$SD_UNIT_NAME" | awk -F= '{print $2}')
+	current=$(systemctl show $user --property $source $SD_UNIT_NAME | awk -F= '{print $2}')
 	echo "systemd $source: current $current !? $expected $expected2"
-	[ "$current" = "$expected" ] || [[ -n "$expected2" && "$current" = "$expected2" ]]
+	[ "$current" = "$expected" ] || [ -n "$expected2" -a "$current" = "$expected2" ]
 }
 
 function check_cpu_quota() {
@@ -309,18 +253,21 @@ function check_cpu_shares() {
 function check_cpu_weight() {
 	local weight=$1
 
-	check_cgroup_value "cpu.weight" "$weight"
-	check_systemd_value "CPUWeight" "$weight"
+	check_cgroup_value "cpu.weight" $weight
+	check_systemd_value "CPUWeight" $weight
 }
 
 # Helper function to set a resources limit
 function set_resources_limit() {
-	update_config '.linux.resources.pids.limit |= 100'
+	bundle="${1:-.}"
+	update_config '.linux.resources.pids.limit |= 100' $bundle
 }
 
 # Helper function to make /sys/fs/cgroup writable
 function set_cgroup_mount_writable() {
-	update_config '.mounts |= map((select(.type == "cgroup") | .options -= ["ro"]) // .)'
+	bundle="${1:-.}"
+	update_config '.mounts |= map((select(.type == "cgroup") | .options -= ["ro"]) // .)' \
+		$bundle
 }
 
 # Fails the current test, providing the error given.
@@ -383,7 +330,7 @@ function requires() {
 			;;
 		cgroups_swap)
 			init_cgroup_paths
-			if [ $CGROUP_UNIFIED = "no" ] && [ ! -e "${CGROUP_MEMORY_BASE_PATH}/memory.memsw.limit_in_bytes" ]; then
+			if [ $CGROUP_UNIFIED = "no" -a ! -e "${CGROUP_MEMORY_BASE_PATH}/memory.memsw.limit_in_bytes" ]; then
 				skip_me=1
 			fi
 			;;
@@ -412,9 +359,8 @@ function requires() {
 			fi
 			;;
 		smp)
-			local cpus
-			cpus=$(grep -c '^processor' /proc/cpuinfo)
-			if [ "$cpus" -lt 2 ]; then
+			local cpu_count=$(grep -c '^processor' /proc/cpuinfo)
+			if [ "$cpu_count" -lt 2 ]; then
 				skip_me=1
 			fi
 			;;
@@ -451,10 +397,10 @@ function retry() {
 		if [[ "$status" -eq 0 ]]; then
 			return 0
 		fi
-		sleep "$delay"
+		sleep $delay
 	done
 
-	echo "Command \"$*\" failed $attempts times. Output: $output"
+	echo "Command \"$@\" failed $attempts times. Output: $output"
 	false
 }
 
@@ -472,8 +418,8 @@ function wait_for_container() {
 
 function testcontainer() {
 	# test state of container
-	runc state "$1"
-	if [ "$2" == "checkpointed" ]; then
+	runc state $1
+	if [ $2 == "checkpointed" ]; then
 		[ "$status" -eq 1 ]
 		return
 	fi
@@ -485,7 +431,7 @@ function setup_recvtty() {
 	[ -z "$ROOT" ] && return 1 # must not be called without ROOT set
 	local dir="$ROOT/tty"
 
-	mkdir "$dir"
+	mkdir $dir
 	export CONSOLE_SOCKET="$dir/sock"
 
 	# We need to start recvtty in the background, so we double fork in the shell.
@@ -498,7 +444,7 @@ function teardown_recvtty() {
 
 	# When we kill recvtty, the container will also be killed.
 	if [ -f "$dir/pid" ]; then
-		kill -9 "$(cat "$dir/pid")"
+		kill -9 $(cat "$dir/pid")
 	fi
 
 	# Clean up the files that might be left over.
@@ -509,11 +455,11 @@ function setup_bundle() {
 	local image="$1"
 
 	# Root for various container directories (state, tty, bundle).
-	ROOT=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX")
+	export ROOT=$(mktemp -d "$BATS_RUN_TMPDIR/runc.XXXXXX")
 	mkdir -p "$ROOT/state" "$ROOT/bundle/rootfs"
 
 	setup_recvtty
-	cd "$ROOT/bundle" || return
+	cd "$ROOT/bundle"
 
 	tar --exclude './dev/*' -C rootfs -xf "$image"
 
@@ -536,12 +482,11 @@ function setup_debian() {
 function teardown_bundle() {
 	[ -z "$ROOT" ] && return 0 # nothing to teardown
 
-	cd "$INTEGRATION_ROOT" || return
+	cd "$INTEGRATION_ROOT"
 	teardown_recvtty
 	local ct
 	for ct in $(__runc list -q); do
 		__runc delete -f "$ct"
 	done
 	rm -rf "$ROOT"
-	remove_parent
 }
diff --git a/tests/integration/run.bats b/tests/integration/run.bats
deleted file mode 100644
index 63be89d2..00000000
--- a/tests/integration/run.bats
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env bats
-
-load helpers
-
-function setup() {
-	setup_hello
-}
-
-function teardown() {
-	teardown_bundle
-}
-
-@test "runc run" {
-	runc run test_hello
-	[ "$status" -eq 0 ]
-
-	runc state test_hello
-	[ "$status" -ne 0 ]
-}
-
-@test "runc run --keep" {
-	runc run --keep test_run_keep
-	[ "$status" -eq 0 ]
-
-	testcontainer test_run_keep stopped
-
-	runc state test_run_keep
-	[ "$status" -eq 0 ]
-
-	runc delete test_run_keep
-
-	runc state test_run_keep
-	[ "$status" -ne 0 ]
-}
-
-@test "runc run --keep (check cgroup exists)" {
-	# for systemd driver, the unit's cgroup path will be auto removed if container's all processes exited
-	requires no_systemd
-
-	[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
-
-	set_cgroups_path
-
-	runc run --keep test_run_keep
-	[ "$status" -eq 0 ]
-
-	testcontainer test_run_keep stopped
-
-	runc state test_run_keep
-	[ "$status" -eq 0 ]
-
-	# check that cgroup exists
-	check_cgroup_value "pids.max" "max"
-
-	runc delete test_run_keep
-
-	runc state test_run_keep
-	[ "$status" -ne 0 ]
-}
diff --git a/tests/integration/update.bats b/tests/integration/update.bats
index 01c69152..2004d9ef 100644
--- a/tests/integration/update.bats
+++ b/tests/integration/update.bats
@@ -345,15 +345,6 @@ EOF
 	check_cpu_quota -1 1000000 "infinity"
 }
 
-@test "set cpu period with no quota (invalid period)" {
-	[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
-
-	update_config '.linux.resources.cpu |= { "period": 100 }'
-
-	runc run -d --console-socket "$CONSOLE_SOCKET" test_update
-	[ "$status" -eq 1 ]
-}
-
 @test "set cpu quota with no period" {
 	[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
 
@@ -392,41 +383,6 @@ EOF
 	check_cpu_quota 30000 100000 "300ms"
 }
 
-@test "update cpu period in a pod cgroup with pod limit set" {
-	requires cgroups_v1
-	[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
-
-	set_cgroups_path "pod_${RANDOM}"
-
-	# Set parent/pod CPU quota limit to 50%.
-	if [ -n "${RUNC_USE_SYSTEMD}" ]; then
-		set_parent_systemd_properties CPUQuota="50%"
-	else
-		echo 50000 >"/sys/fs/cgroup/cpu/$REL_PARENT_PATH/cpu.cfs_quota_us"
-	fi
-	# Sanity checks.
-	run cat "/sys/fs/cgroup/cpu$REL_PARENT_PATH/cpu.cfs_period_us"
-	[ "$output" -eq 100000 ]
-	run cat "/sys/fs/cgroup/cpu$REL_PARENT_PATH/cpu.cfs_quota_us"
-	[ "$output" -eq 50000 ]
-
-	runc run -d --console-socket "$CONSOLE_SOCKET" test_update
-	[ "$status" -eq 0 ]
-	# Get the current period.
-	local cur
-	cur=$(get_cgroup_value cpu.cfs_period_us)
-
-	# Sanity check: as the parent cgroup sets the limit to 50%,
-	# setting a higher limit (e.g. 60%) is expected to fail.
-	runc update --cpu-quota $((cur * 6 / 10)) test_update
-	[ "$status" -eq 1 ]
-
-	# Finally, the test itself: set 30% limit but with lower period.
-	runc update --cpu-period 10000 --cpu-quota 3000 test_update
-	[ "$status" -eq 0 ]
-	check_cpu_quota 3000 10000 "300ms"
-}
-
 @test "update cgroup v2 resources via unified map" {
 	[[ "$ROOTLESS" -ne 0 ]] && requires rootless_cgroup
 	requires cgroups_v2