Repository: opencontainers/runc
Pull request #2696 (2020-11-30 20:39:33)

libct/system.Stat: fix panic, speed up, add benchmarks
#### 1.    libct/system/proc_test: fix, improve, add benchmarks
    
1. Add a test case that tests parentheses in command.
    
2. Replace individual comparisons with `reflect.DeepEqual`.
   This also fixes wrong `%`-style types used in `Fatalf` statements.
    
3. Replace `Fatalf` with `Errorf` so we don't bail out on the first
   failure, and do not check result on error.
    
4. Add two benchmarks.

#### 2. libct/system.Stat: fix/improve/speedup

1. Remove PID field. It is totally useless -- we already know the PID.
    
2. Rewrite `parseStat()` to make it faster and more correct:
   - do not use `fmt.Scanf` as it is very slow;
   - avoid splitting data into 20+ fields, of which we only need 2 (should also help garbage collector);
   - make sure to not panic on short lines and other bad input;
   - add some bad input tests (some fail with old code);
   - use `LastIndexByte` instead of `LastIndex` where appropriate.
 
Benchmarks:

before (from the previous commit message):

```    
BenchmarkParseStat-4              116415             10804 ns/op
BenchmarkParseRealStat-4             240           4781769 ns/op
```
    
after:
```    
BenchmarkParseStat-4           1164948              1068 ns/op
BenchmarkParseRealStat-4           331           3458315 ns/op
```    

We are seeing 10x speedup in a synthetic benchmark, and about 1.4x
speedup in a real world benchmark. All this while being more scrupulous
about any possible errors. I also suspect much less allocations and thus
garbage to collect.
     
While at it, do not ignore any possible errors, and properly wrap those.

3. Add `I` and `P` process states (available since Linux 4.14).

## History
    
* v2: use pkg/errors more, remove t.Logf from test
* v3: rebased; drop pkg/errors; gofumpt'ed
* v4: rebased; improved description
* v5: rebased; mention bad input tests, added second benchmark results
* v6: remove PID field, do not use strings.Split, further speedup.
* v7: readability improvements, added new process states.
* v8: improved parser to bail earier on incorrect data, more doc, more tests.

kolyshkin: Rebased to benefit from #2690 (while Travis was not able to test this PR for 6+ hours :cry:) 
kolyshkin: @AkihiroSuda @mrunalp PTAL
kolyshkin: rebased (for new CI)
cyphar: Is this a bugfix or an improvement? If it's an improvement it belongs in post-1.0 as a milestone.
kolyshkin: > Is this a bugfix or an improvement? If it's an improvement it belongs in post-1.0 as a milestone.

Improvement (except eliminating a linter warning which might be classified as bugfix), and that's lots of code, so moving to post 1.0
cyphar: Yeah agreed, this change looks a little scary tbh.
kolyshkin: close/open to re-kick ci
thaJeztah: linter is failing;

```
 Error: File is not `gofumpt`-ed (gofumpt)
```
kolyshkin: v3: rebased; drop using pkg/errors; gofumpt'ed
kolyshkin: CI failure is unrelated (https://github.com/opencontainers/runc/issues/3050)
kolyshkin: Rebased to fix CI.
kolyshkin: Some quite interesting observations:
* removing `strings.Split` (which result in ~20 fields slice) and a couple of `fmt.Scanf`s results in 7x improvement!
* removing `strings.Split` (for much shorter data, only the first two fields) results in further 2x improvement!
kolyshkin: @thaJeztah PTAL

Patch:
diff --git a/.cirrus.yml b/.cirrus.yml
index 26a579cf..74b7deae 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -58,9 +58,17 @@ task:
   integration_fs_script: |
     ssh -tt default "sudo -i make -C /vagrant localintegration"
   integration_systemd_rootless_script: |
-    ssh -tt default "sudo -i make -C /vagrant localrootlessintegration RUNC_USE_SYSTEMD=yes"
+    if [ $DISTRO == centos7 ]; then
+      echo "SKIP: integration_systemd_rootless_script requires cgroup v2"
+    else
+      ssh -tt default "sudo -i make -C /vagrant localrootlessintegration RUNC_USE_SYSTEMD=yes"
+    fi
   integration_fs_rootless_script: |
-    ssh -tt default "sudo -i make -C /vagrant localrootlessintegration"
+    if [ $DISTRO == centos7 ]; then
+      echo "SKIP: FIXME: integration_fs_rootless_script is skipped because of EPERM on writing cgroup.procs"
+    else
+      ssh -tt default "sudo -i make -C /vagrant localrootlessintegration"
+    fi
 
 task:
   timeout_in: 30m
@@ -85,6 +93,7 @@ task:
     memory: 8G
 
   install_dependencies_script: |
+    yum install -y -q epel-release
     case $DISTRO in
     centos-7)
       (cd /etc/yum.repos.d && curl -O https://copr.fedorainfracloud.org/coprs/adrian/criu-el7/repo/epel-7/adrian-criu-el7-epel-7.repo)
@@ -93,15 +102,11 @@ task:
       sysctl --system
       ;;
     centos-stream-8)
-      yum config-manager --set-enabled powertools # for glibc-static
+      yum install -y -q dnf-plugins-core
+      yum config-manager --set-enabled powertools
       ;;
     esac
-    # Work around dnf mirror failures by retrying a few times.
-    for i in $(seq 0 2); do
-      sleep $i
-      yum install -y -q gcc git iptables jq glibc-static libseccomp-devel make criu && break
-    done
-    [ $? -eq 0 ] # fail if yum failed
+    yum install -y -q gcc git iptables jq glibc-static libseccomp-devel make criu
     # install Go
     curl -fsSL "https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz" | tar Cxz /usr/local
     # install bats
diff --git a/.codespellrc b/.codespellrc
deleted file mode 100644
index c626cb1f..00000000
--- a/.codespellrc
+++ /dev/null
@@ -1,3 +0,0 @@
-[codespell]
-skip = ./vendor,./.git
-ignore-words-list = clos,creat
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 8ad6be73..fa812413 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -23,23 +23,6 @@ jobs:
           # must be specified without patch version
           version: v1.40
 
-  compile-buildtags:
-    runs-on: ubuntu-20.04
-    steps:
-      - uses: actions/checkout@v2
-      - name: compile with no build tags
-        run: make BUILDTAGS=""
-
-  codespell:
-    runs-on: ubuntu-20.04
-    steps:
-    - uses: actions/checkout@v2
-    - name: install deps
-      # Version of codespell bundled with Ubuntu is way old, so use pip.
-      run: pip install codespell
-    - name: run codespell
-      run: codespell
-
   shfmt:
     runs-on: ubuntu-20.04
     steps:
diff --git a/Dockerfile b/Dockerfile
index d98c53d5..2266903c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
-ARG GO_VERSION=1.17
+ARG GO_VERSION=1.16
 ARG BATS_VERSION=v1.3.0
 
-FROM golang:${GO_VERSION}-bullseye
+FROM golang:${GO_VERSION}-buster
 ARG DEBIAN_FRONTEND=noninteractive
 
 RUN echo 'deb https://download.opensuse.org/repositories/devel:/tools:/criu/Debian_10/ /' > /etc/apt/sources.list.d/criu.list \
@@ -31,7 +31,7 @@ RUN echo 'deb https://download.opensuse.org/repositories/devel:/tools:/criu/Debi
         libseccomp-dev:ppc64el \
         libseccomp2 \
         pkg-config \
-        python3-minimal \
+        python-minimal \
         sudo \
         uidmap \
     && apt-get clean \
diff --git a/MAINTAINERS b/MAINTAINERS
index e7fa530b..17e93004 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5,4 +5,3 @@ Qiang Huang <h.huangqiang@huawei.com> (@hqhq)
 Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
 Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp> (@AkihiroSuda)
 Kir Kolyshkin <kolyshkin@gmail.com> (@kolyshkin)
-Sebastiaan van Stijn <github@gone.nl> (@thaJeztah)
diff --git a/docs/systemd.md b/docs/systemd.md
index c74e2e2f..bbd7ab98 100644
--- a/docs/systemd.md
+++ b/docs/systemd.md
@@ -3,7 +3,7 @@
 By default, runc creates cgroups and sets cgroup limits on its own (this mode
 is known as fs cgroup driver). When `--systemd-cgroup` global option is given
 (as in e.g. `runc --systemd-cgroup run ...`), runc switches to systemd cgroup
-driver. This document describes its features and peculiarities.
+driver. This document describes its features and pecularities.
 
 ### systemd unit name and placement
 
diff --git a/exec.go b/exec.go
index f8baff3d..891fabbd 100644
--- a/exec.go
+++ b/exec.go
@@ -101,8 +101,7 @@ following will output a list of processes running in the container:
 		if err == nil {
 			os.Exit(status)
 		}
-		fatalWithCode(fmt.Errorf("exec failed: %w", err), 255)
-		return nil // to satisfy the linter
+		return fmt.Errorf("exec failed: %w", err)
 	},
 	SkipArgReorder: true,
 }
diff --git a/go.mod b/go.mod
index a45e2da8..32748263 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/bits-and-blooms/bitset v1.2.0
 	github.com/checkpoint-restore/go-criu/v5 v5.0.0
 	github.com/cilium/ebpf v0.6.2
-	github.com/containerd/console v1.0.3
+	github.com/containerd/console v1.0.2
 	github.com/coreos/go-systemd/v22 v22.3.2
 	github.com/cyphar/filepath-securejoin v0.2.3
 	github.com/docker/go-units v0.4.0
@@ -14,7 +14,7 @@ require (
 	github.com/moby/sys/mountinfo v0.4.1
 	github.com/mrunalp/fileutils v0.5.0
 	github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
-	github.com/opencontainers/selinux v1.8.4
+	github.com/opencontainers/selinux v1.8.3
 	github.com/seccomp/libseccomp-golang v0.9.1
 	github.com/sirupsen/logrus v1.8.1
 	github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
diff --git a/go.sum b/go.sum
index 7937c033..19078580 100644
--- a/go.sum
+++ b/go.sum
@@ -5,8 +5,8 @@ github.com/checkpoint-restore/go-criu/v5 v5.0.0 h1:TW8f/UvntYoVDMN1K2HlT82qH1rb0
 github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
 github.com/cilium/ebpf v0.6.2 h1:iHsfF/t4aW4heW2YKfeHrVPGdtYTL4C4KocpM8KTSnI=
 github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
-github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
-github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
+github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE=
+github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
 github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
@@ -46,8 +46,10 @@ github.com/mrunalp/fileutils v0.5.0 h1:NKzVxiH7eSk+OQ4M+ZYW1K6h27RUV3MI6NUTsHhU6
 github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc=
 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/selinux v1.8.4 h1:krlgQ6/j9CkCXT5oW0yVXdQFOME3NjKuuAZXuR6O7P4=
-github.com/opencontainers/selinux v1.8.4/go.mod h1:HTvjPFoGMbpQsG886e3lQwnsRWtE4TC1OF3OUvG9FAo=
+github.com/opencontainers/selinux v1.8.3 h1:tzZR7AuKB5gU1+53uBkoG4XdIFGZzvJTOVoNbRQI8/4=
+github.com/opencontainers/selinux v1.8.3/go.mod h1:HTvjPFoGMbpQsG886e3lQwnsRWtE4TC1OF3OUvG9FAo=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
diff --git a/libcontainer/cgroups/devices/devices_emulator.go b/libcontainer/cgroups/devices/devices_emulator.go
index 668dcd41..ebb8b6cd 100644
--- a/libcontainer/cgroups/devices/devices_emulator.go
+++ b/libcontainer/cgroups/devices/devices_emulator.go
@@ -305,7 +305,7 @@ func EmulatorFromList(list io.Reader) (*Emulator, error) {
 // necessary.
 //
 // This function is the sole reason for all of Emulator -- to allow us
-// to figure out how to update a containers' cgroups without causing spurious
+// to figure out how to update a containers' cgroups without causing spurrious
 // device errors (if possible).
 func (source *Emulator) Transition(target *Emulator) ([]*devices.Rule, error) {
 	var transitionRules []*devices.Rule
diff --git a/libcontainer/cgroups/ebpf/ebpf_linux.go b/libcontainer/cgroups/ebpf/ebpf_linux.go
index 104c74a8..5fc230bf 100644
--- a/libcontainer/cgroups/ebpf/ebpf_linux.go
+++ b/libcontainer/cgroups/ebpf/ebpf_linux.go
@@ -134,7 +134,7 @@ func haveBpfProgReplace() bool {
 			// not supported
 			return
 		}
-		// attach_flags test succeeded.
+		// attach_flags test succeded.
 		if !errors.Is(err, unix.EBADF) {
 			logrus.Debugf("checking for BPF_F_REPLACE: got unexpected (not EBADF or EINVAL) error: %v", err)
 		}
diff --git a/libcontainer/cgroups/fs/cpuset.go b/libcontainer/cgroups/fs/cpuset.go
index 8c49a300..c4062b7b 100644
--- a/libcontainer/cgroups/fs/cpuset.go
+++ b/libcontainer/cgroups/fs/cpuset.go
@@ -51,16 +51,16 @@ func getCpusetStat(path string, file string) ([]uint16, error) {
 	}
 
 	for _, s := range strings.Split(fileContent, ",") {
-		sp := strings.SplitN(s, "-", 3)
-		switch len(sp) {
+		splitted := strings.SplitN(s, "-", 3)
+		switch len(splitted) {
 		case 3:
 			return extracted, &parseError{Path: path, File: file, Err: errors.New("extra dash")}
 		case 2:
-			min, err := strconv.ParseUint(sp[0], 10, 16)
+			min, err := strconv.ParseUint(splitted[0], 10, 16)
 			if err != nil {
 				return extracted, &parseError{Path: path, File: file, Err: err}
 			}
-			max, err := strconv.ParseUint(sp[1], 10, 16)
+			max, err := strconv.ParseUint(splitted[1], 10, 16)
 			if err != nil {
 				return extracted, &parseError{Path: path, File: file, Err: err}
 			}
diff --git a/libcontainer/cgroups/getallpids.go b/libcontainer/cgroups/getallpids.go
deleted file mode 100644
index b218744e..00000000
--- a/libcontainer/cgroups/getallpids.go
+++ /dev/null
@@ -1,29 +0,0 @@
-// +build linux,go1.16
-
-package cgroups
-
-import (
-	"io/fs"
-	"path/filepath"
-)
-
-// GetAllPids returns all pids from the cgroup identified by path, and all its
-// sub-cgroups.
-func GetAllPids(path string) ([]int, error) {
-	var pids []int
-	err := filepath.WalkDir(path, func(p string, d fs.DirEntry, iErr error) error {
-		if iErr != nil {
-			return iErr
-		}
-		if !d.IsDir() {
-			return nil
-		}
-		cPids, err := readProcsFile(p)
-		if err != nil {
-			return err
-		}
-		pids = append(pids, cPids...)
-		return nil
-	})
-	return pids, err
-}
diff --git a/libcontainer/cgroups/getallpids_go115.go b/libcontainer/cgroups/getallpids_go115.go
deleted file mode 100644
index 5d55a685..00000000
--- a/libcontainer/cgroups/getallpids_go115.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// +build linux,!go1.16
-
-package cgroups
-
-import (
-	"os"
-	"path/filepath"
-)
-
-// GetAllPids returns all pids, that were added to cgroup at path and to all its
-// subcgroups.
-func GetAllPids(path string) ([]int, error) {
-	var pids []int
-	// collect pids from all sub-cgroups
-	err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error {
-		if iErr != nil {
-			return iErr
-		}
-		if !info.IsDir() {
-			return nil
-		}
-		cPids, err := readProcsFile(p)
-		if err != nil {
-			return err
-		}
-		pids = append(pids, cPids...)
-		return nil
-	})
-	return pids, err
-}
diff --git a/libcontainer/cgroups/stats.go b/libcontainer/cgroups/stats.go
index d2e68efe..e7f9c462 100644
--- a/libcontainer/cgroups/stats.go
+++ b/libcontainer/cgroups/stats.go
@@ -126,7 +126,7 @@ type BlkioStatEntry struct {
 }
 
 type BlkioStats struct {
-	// number of bytes transferred to and from the block device
+	// number of bytes tranferred to and from the block device
 	IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"`
 	IoServicedRecursive     []BlkioStatEntry `json:"io_serviced_recursive,omitempty"`
 	IoQueuedRecursive       []BlkioStatEntry `json:"io_queue_recursive,omitempty"`
diff --git a/libcontainer/cgroups/systemd/common.go b/libcontainer/cgroups/systemd/common.go
index 98ccc516..125f6f55 100644
--- a/libcontainer/cgroups/systemd/common.go
+++ b/libcontainer/cgroups/systemd/common.go
@@ -198,7 +198,7 @@ func generateDeviceProperties(r *configs.Resources) ([]systemdDbus.Property, err
 	}
 	// systemd doesn't support blacklists. So we log a warning, and tell
 	// systemd to act as a deny-all whitelist. This ruleset will be replaced
-	// with our normal fallback code. This may result in spurious errors, but
+	// with our normal fallback code. This may result in spurrious errors, but
 	// the only other option is to error out here.
 	if configEmu.IsBlacklist() {
 		// However, if we're dealing with an allow-all rule then we can do it.
@@ -311,14 +311,6 @@ func getUnitName(c *configs.Cgroup) string {
 	return c.Name
 }
 
-// This code should be in sync with getUnitName.
-func getUnitType(unitName string) string {
-	if strings.HasSuffix(unitName, ".slice") {
-		return "Slice"
-	}
-	return "Scope"
-}
-
 // isDbusError returns true if the error is a specific dbus error.
 func isDbusError(err error, name string) bool {
 	if err != nil {
@@ -397,10 +389,10 @@ func resetFailedUnit(cm *dbusConnManager, name string) {
 	}
 }
 
-func getUnitTypeProperty(cm *dbusConnManager, unitName string, unitType string, propertyName string) (*systemdDbus.Property, error) {
+func getUnitProperty(cm *dbusConnManager, unitName string, propertyName string) (*systemdDbus.Property, error) {
 	var prop *systemdDbus.Property
 	err := cm.retryOnDisconnect(func(c *systemdDbus.Conn) (Err error) {
-		prop, Err = c.GetUnitTypePropertyContext(context.TODO(), unitName, unitType, propertyName)
+		prop, Err = c.GetUnitPropertyContext(context.TODO(), unitName, propertyName)
 		return Err
 	})
 	return prop, err
diff --git a/libcontainer/cgroups/systemd/v1.go b/libcontainer/cgroups/systemd/v1.go
index cd4720c5..1a8e1e3c 100644
--- a/libcontainer/cgroups/systemd/v1.go
+++ b/libcontainer/cgroups/systemd/v1.go
@@ -6,7 +6,6 @@ import (
 	"errors"
 	"os"
 	"path/filepath"
-	"reflect"
 	"strings"
 	"sync"
 
@@ -346,11 +345,6 @@ func (m *legacyManager) freezeBeforeSet(unitName string, r *configs.Resources) (
 	// Special case for SkipDevices, as used by Kubernetes to create pod
 	// cgroups with allow-all device policy).
 	if r.SkipDevices {
-		if r.SkipFreezeOnSet {
-			// Both needsFreeze and needsThaw are false.
-			return
-		}
-
 		// No need to freeze if SkipDevices is set, and either
 		// (1) systemd unit does not (yet) exist, or
 		// (2) it has DevicePolicy=auto and empty DeviceAllow list.
@@ -359,20 +353,15 @@ func (m *legacyManager) freezeBeforeSet(unitName string, r *configs.Resources) (
 		// a non-existent unit returns default properties,
 		// and settings in (2) are the defaults.
 		//
-		// Do not return errors from getUnitTypeProperty, as they alone
+		// Do not return errors from getUnitProperty, as they alone
 		// should not prevent Set from working.
-
-		unitType := getUnitType(unitName)
-
-		devPolicy, e := getUnitTypeProperty(m.dbus, unitName, unitType, "DevicePolicy")
+		devPolicy, e := getUnitProperty(m.dbus, unitName, "DevicePolicy")
 		if e == nil && devPolicy.Value == dbus.MakeVariant("auto") {
-			devAllow, e := getUnitTypeProperty(m.dbus, unitName, unitType, "DeviceAllow")
-			if e == nil {
-				if rv := reflect.ValueOf(devAllow.Value.Value()); rv.Kind() == reflect.Slice && rv.Len() == 0 {
-					needsFreeze = false
-					needsThaw = false
-					return
-				}
+			devAllow, e := getUnitProperty(m.dbus, unitName, "DeviceAllow")
+			if e == nil && devAllow.Value == dbus.MakeVariant([]deviceAllowEntry{}) {
+				needsFreeze = false
+				needsThaw = false
+				return
 			}
 		}
 	}
diff --git a/libcontainer/cgroups/utils.go b/libcontainer/cgroups/utils.go
index b31705be..75015d66 100644
--- a/libcontainer/cgroups/utils.go
+++ b/libcontainer/cgroups/utils.go
@@ -118,8 +118,8 @@ func GetAllSubsystems() ([]string, error) {
 	return subsystems, nil
 }
 
-func readProcsFile(dir string) ([]int, error) {
-	f, err := OpenFile(dir, CgroupProcesses, os.O_RDONLY)
+func readProcsFile(file string) ([]int, error) {
+	f, err := os.Open(file)
 	if err != nil {
 		return nil, err
 	}
@@ -336,7 +336,29 @@ func getHugePageSizeFromFilenames(fileNames []string) ([]string, error) {
 
 // GetPids returns all pids, that were added to cgroup at path.
 func GetPids(dir string) ([]int, error) {
-	return readProcsFile(dir)
+	return readProcsFile(filepath.Join(dir, CgroupProcesses))
+}
+
+// GetAllPids returns all pids, that were added to cgroup at path and to all its
+// subcgroups.
+func GetAllPids(path string) ([]int, error) {
+	var pids []int
+	// collect pids from all sub-cgroups
+	err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error {
+		if iErr != nil {
+			return iErr
+		}
+		if info.IsDir() || info.Name() != CgroupProcesses {
+			return nil
+		}
+		cPids, err := readProcsFile(p)
+		if err != nil {
+			return err
+		}
+		pids = append(pids, cPids...)
+		return nil
+	})
+	return pids, err
 }
 
 // WriteCgroupProc writes the specified pid into the cgroup's cgroup.procs file
diff --git a/libcontainer/configs/cgroup_linux.go b/libcontainer/configs/cgroup_linux.go
index 5ea9d940..a1e7f0af 100644
--- a/libcontainer/configs/cgroup_linux.go
+++ b/libcontainer/configs/cgroup_linux.go
@@ -131,16 +131,4 @@ type Resources struct {
 	//
 	// NOTE it is impossible to start a container which has this flag set.
 	SkipDevices bool `json:"-"`
-
-	// SkipFreezeOnSet is a flag for cgroup manager to skip the cgroup
-	// freeze when setting resources. Only applicable to systemd legacy
-	// (i.e. cgroup v1) manager (which uses freeze by default to avoid
-	// spurious permission errors caused by systemd inability to update
-	// device rules in a non-disruptive manner).
-	//
-	// If not set, a few methods (such as looking into cgroup's
-	// devices.list and querying the systemd unit properties) are used
-	// during Set() to figure out whether the freeze is required. Those
-	// methods may be relatively slow, thus this flag.
-	SkipFreezeOnSet bool `json:"-"`
 }
diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go
index 21e77932..19a82bbf 100644
--- a/libcontainer/container_linux.go
+++ b/libcontainer/container_linux.go
@@ -2100,7 +2100,7 @@ func ignoreTerminateErrors(err error) error {
 	if err == nil {
 		return nil
 	}
-	// terminate() might return an error from either Kill or Wait.
+	// terminate() might return an error from ether Kill or Wait.
 	// The (*Cmd).Wait documentation says: "If the command fails to run
 	// or doesn't complete successfully, the error is of type *ExitError".
 	// Filter out such errors (like "exit status 1" or "signal: killed").
diff --git a/libcontainer/nsenter/nsexec.c b/libcontainer/nsenter/nsexec.c
index 23861a84..49e8f54b 100644
--- a/libcontainer/nsenter/nsexec.c
+++ b/libcontainer/nsenter/nsexec.c
@@ -136,7 +136,7 @@ int setns(int fd, int nstype)
 
 static void write_log(const char *level, const char *format, ...)
 {
-	char *message = NULL, *stage = NULL, *json = NULL;
+	char *message = NULL, *stage = NULL;
 	va_list args;
 	int ret;
 
@@ -146,10 +146,8 @@ static void write_log(const char *level, const char *format, ...)
 	va_start(args, format);
 	ret = vasprintf(&message, format, args);
 	va_end(args);
-	if (ret < 0) {
-		message = NULL;
+	if (ret < 0)
 		goto out;
-	}
 
 	message = escape_json_string(message);
 
@@ -157,26 +155,14 @@ static void write_log(const char *level, const char *format, ...)
 		stage = strdup("nsexec");
 	else
 		ret = asprintf(&stage, "nsexec-%d", current_stage);
-	if (ret < 0) {
-		stage = NULL;
-		goto out;
-	}
-
-	ret = asprintf(&json, "{\"level\":\"%s\", \"msg\": \"%s[%d]: %s\"}\n", level, stage, getpid(), message);
-	if (ret < 0) {
-		json = NULL;
+	if (ret < 0)
 		goto out;
-	}
 
-	/* This logging is on a best-effort basis. In case of a short or failed
-	 * write there is nothing we can do, so just ignore write() errors.
-	 */
-	ssize_t __attribute__((unused)) __res = write(logfd, json, ret);
+	dprintf(logfd, "{\"level\":\"%s\", \"msg\": \"%s[%d]: %s\"}\n", level, stage, getpid(), message);
 
 out:
 	free(message);
 	free(stage);
-	free(json);
 }
 
 /* XXX: This is ugly. */
@@ -318,7 +304,7 @@ static int try_mapping_tool(const char *app, int pid, char *map, size_t map_len)
 
 static void update_uidmap(const char *path, int pid, char *map, size_t map_len)
 {
-	if (map == NULL || map_len == 0)
+	if (map == NULL || map_len <= 0)
 		return;
 
 	write_log(DEBUG, "update /proc/%d/uid_map to '%s'", pid, map);
@@ -333,7 +319,7 @@ static void update_uidmap(const char *path, int pid, char *map, size_t map_len)
 
 static void update_gidmap(const char *path, int pid, char *map, size_t map_len)
 {
-	if (map == NULL || map_len == 0)
+	if (map == NULL || map_len <= 0)
 		return;
 
 	write_log(DEBUG, "update /proc/%d/gid_map to '%s'", pid, map);
@@ -348,7 +334,7 @@ static void update_gidmap(const char *path, int pid, char *map, size_t map_len)
 
 static void update_oom_score_adj(char *data, size_t len)
 {
-	if (data == NULL || len == 0)
+	if (data == NULL || len <= 0)
 		return;
 
 	write_log(DEBUG, "update /proc/self/oom_score_adj to '%s'", data);
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
index 3c3fc0d1..361ce9d3 100644
--- a/libcontainer/rootfs_linux.go
+++ b/libcontainer/rootfs_linux.go
@@ -864,7 +864,7 @@ func pivotRoot(rootfs string) error {
 	if err := mount("", ".", "", "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil {
 		return err
 	}
-	// Perform the unmount. MNT_DETACH allows us to unmount /proc/self/cwd.
+	// Preform the unmount. MNT_DETACH allows us to unmount /proc/self/cwd.
 	if err := unmount(".", unix.MNT_DETACH); err != nil {
 		return err
 	}
diff --git a/libcontainer/seccomp/config.go b/libcontainer/seccomp/config.go
index 2e5adfd3..b54b7eea 100644
--- a/libcontainer/seccomp/config.go
+++ b/libcontainer/seccomp/config.go
@@ -56,7 +56,9 @@ func ConvertStringToOperator(in string) (configs.Operator, error) {
 }
 
 // ConvertStringToAction converts a string into a Seccomp rule match action.
-// Actions use the names they are assigned in Libseccomp's header.
+// Actions use the names they are assigned in Libseccomp's header, though some
+// (notable, SCMP_ACT_TRACE) are not available in this implementation and will
+// return errors.
 // Attempting to convert a string that is not a valid action results in an
 // error.
 func ConvertStringToAction(in string) (configs.Action, error) {
diff --git a/libcontainer/seccomp/seccomp_linux.go b/libcontainer/seccomp/seccomp_linux.go
index 58cad902..fbbe8219 100644
--- a/libcontainer/seccomp/seccomp_linux.go
+++ b/libcontainer/seccomp/seccomp_linux.go
@@ -6,12 +6,11 @@ import (
 	"errors"
 	"fmt"
 
-	libseccomp "github.com/seccomp/libseccomp-golang"
-	"github.com/sirupsen/logrus"
-	"golang.org/x/sys/unix"
-
 	"github.com/opencontainers/runc/libcontainer/configs"
 	"github.com/opencontainers/runc/libcontainer/seccomp/patchbpf"
+
+	libseccomp "github.com/seccomp/libseccomp-golang"
+	"golang.org/x/sys/unix"
 )
 
 var (
@@ -68,7 +67,7 @@ func InitSeccomp(config *configs.Seccomp) error {
 		if call == nil {
 			return errors.New("encountered nil syscall while initializing Seccomp")
 		}
-		if err := matchCall(filter, call, defaultAction); err != nil {
+		if err := matchCall(filter, call); err != nil {
 			return err
 		}
 	}
@@ -143,7 +142,7 @@ func getCondition(arg *configs.Arg) (libseccomp.ScmpCondition, error) {
 }
 
 // Add a rule to match a single syscall
-func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall, defAct libseccomp.ScmpAction) error {
+func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall) error {
 	if call == nil || filter == nil {
 		return errors.New("cannot use nil as syscall to block")
 	}
@@ -152,23 +151,17 @@ func matchCall(filter *libseccomp.ScmpFilter, call *configs.Syscall, defAct libs
 		return errors.New("empty string is not a valid syscall")
 	}
 
-	// Convert the call's action to the libseccomp equivalent
-	callAct, err := getAction(call.Action, call.ErrnoRet)
+	// If we can't resolve the syscall, assume it's not supported on this kernel
+	// Ignore it, don't error out
+	callNum, err := libseccomp.GetSyscallFromName(call.Name)
 	if err != nil {
-		return fmt.Errorf("action in seccomp profile is invalid: %w", err)
-	}
-	if callAct == defAct {
-		// This rule is redundant, silently skip it
-		// to avoid error from AddRule.
 		return nil
 	}
 
-	// If we can't resolve the syscall, assume it is not supported
-	// by this kernel. Warn about it, don't error out.
-	callNum, err := libseccomp.GetSyscallFromName(call.Name)
+	// Convert the call's action to the libseccomp equivalent
+	callAct, err := getAction(call.Action, call.ErrnoRet)
 	if err != nil {
-		logrus.Debugf("unknown seccomp syscall %q ignored", call.Name)
-		return nil
+		return fmt.Errorf("action in seccomp profile is invalid: %w", err)
 	}
 
 	// Unconditional match - just add the rule
diff --git a/libcontainer/standard_init_linux.go b/libcontainer/standard_init_linux.go
index f1e20207..19892d15 100644
--- a/libcontainer/standard_init_linux.go
+++ b/libcontainer/standard_init_linux.go
@@ -71,7 +71,7 @@ func (l *linuxStandardInit) Init() error {
 				return fmt.Errorf("unable to join session keyring: %w", err)
 			}
 		} else {
-			// Make session keyring searchable. If we've gotten this far we
+			// Make session keyring searcheable. If we've gotten this far we
 			// bail on any error -- we don't want to have a keyring with bad
 			// permissions.
 			if err := keys.ModKeyringPerm(sessKeyId, keepperms, newperms); err != nil {
diff --git a/libcontainer/system/proc.go b/libcontainer/system/proc.go
index d0407cfe..8f0a68af 100644
--- a/libcontainer/system/proc.go
+++ b/libcontainer/system/proc.go
@@ -19,6 +19,8 @@ const ( // Only values for Linux 3.14 and later are listed here
 	Stopped     State = 'T'
 	TracingStop State = 't'
 	Zombie      State = 'Z'
+	Parked      State = 'P'
+	Idle        State = 'I'
 )
 
 // String forms of the state from proc(5)'s documentation for
@@ -39,6 +41,10 @@ func (s State) String() string {
 		return "tracing stop"
 	case Zombie:
 		return "zombie"
+	case Parked:
+		return "parked"
+	case Idle:
+		return "idle" // kernel thread
 	default:
 		return fmt.Sprintf("unknown (%c)", s)
 	}
@@ -48,9 +54,6 @@ func (s State) String() string {
 // described in proc(5) with names based on the /proc/[pid]/status
 // fields.
 type Stat_t struct {
-	// PID is the process ID.
-	PID uint
-
 	// Name is the command run by the process.
 	Name string
 
@@ -72,32 +75,53 @@ func Stat(pid int) (stat Stat_t, err error) {
 }
 
 func parseStat(data string) (stat Stat_t, err error) {
-	// From proc(5), field 2 could contain space and is inside `(` and `)`.
-	// The following is an example:
+	// Example:
 	// 89653 (gunicorn: maste) S 89630 89653 89653 0 -1 4194560 29689 28896 0 3 146 32 76 19 20 0 1 0 2971844 52965376 3920 18446744073709551615 1 1 0 0 0 0 0 16781312 137447943 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0
-	i := strings.LastIndex(data, ")")
-	if i <= 2 || i >= len(data)-1 {
-		return stat, fmt.Errorf("invalid stat data: %q", data)
+	// The fields are space-separated, see full description in proc(5).
+	//
+	// We are only interested in:
+	//  * field 2: process name. It is the only field enclosed into
+	//    parenthesis, as it can contain spaces (and parenthesis) inside.
+	//  * field 3: process state, a single character (%c)
+	//  * field 22: process start time, a long unsigned integer (%llu).
+
+	// 1. Look for the first '(' and the last ')' first, what's in between is Name.
+	//    We expect at least 20 fields and a space after the last one.
+
+	const minAfterName = 20*2 + 1 // the min field is '0 '.
+
+	first := strings.IndexByte(data, '(')
+	if first < 0 || first+minAfterName >= len(data) {
+		return stat, fmt.Errorf("invalid stat data (no comm or too short): %q", data)
 	}
 
-	parts := strings.SplitN(data[:i], "(", 2)
-	if len(parts) != 2 {
-		return stat, fmt.Errorf("invalid stat data: %q", data)
+	last := strings.LastIndexByte(data, ')')
+	if last <= first || last+minAfterName >= len(data) {
+		return stat, fmt.Errorf("invalid stat data (no comm or too short): %q", data)
 	}
 
-	stat.Name = parts[1]
-	_, err = fmt.Sscanf(parts[0], "%d", &stat.PID)
+	stat.Name = data[first+1 : last]
+
+	// 2. Remove fields 1 and 2 and a space after. State is right after.
+	data = data[last+2:]
+	stat.State = State(data[0])
+
+	// 3. StartTime is field 22, data is at field 3 now, so we need to skip 19 spaces.
+	skipSpaces := 22 - 3
+	for first = 0; skipSpaces > 0 && first < len(data); first++ {
+		if data[first] == ' ' {
+			skipSpaces--
+		}
+	}
+	// Now first points to StartTime; look for space right after.
+	i := strings.IndexByte(data[first:], ' ')
+	if i < 0 {
+		return stat, fmt.Errorf("invalid stat data (too short): %q", data)
+	}
+	stat.StartTime, err = strconv.ParseUint(data[first:first+i], 10, 64)
 	if err != nil {
-		return stat, err
+		return stat, fmt.Errorf("invalid stat data (bad start time): %w", err)
 	}
 
-	// parts indexes should be offset by 3 from the field number given
-	// proc(5), because parts is zero-indexed and we've removed fields
-	// one (PID) and two (Name) in the paren-split.
-	parts = strings.Split(data[i+2:], " ")
-	var state int
-	fmt.Sscanf(parts[3-3], "%c", &state) //nolint:staticcheck // "3-3" is more readable in this context.
-	stat.State = State(state)
-	fmt.Sscanf(parts[22-3], "%d", &stat.StartTime)
 	return stat, nil
 }
diff --git a/man/runc-exec.8.md b/man/runc-exec.8.md
index 49f9cab5..dafd03dc 100644
--- a/man/runc-exec.8.md
+++ b/man/runc-exec.8.md
@@ -59,11 +59,6 @@ multiple times.
 : Pass _N_ additional file descriptors to the container (**stdio** +
 **$LISTEN_FDS** + _N_ in total). Default is **0**.
 
-# EXIT STATUS
-
-Exits with a status of _command_ (unless **-d** is used), or **255** if
-an error occurred.
-
 # EXAMPLES
 If the container can run **ps**(1) command, the following
 will output a list of processes running in the container:
diff --git a/update.go b/update.go
index 7db5879d..e678e220 100644
--- a/update.go
+++ b/update.go
@@ -260,7 +260,7 @@ other options are ignored.
 		// Update the values
 		config.Cgroups.Resources.BlkioWeight = *r.BlockIO.Weight
 
-		// Setting CPU quota and period independently does not make much sense,
+		// Seting CPU quota and period independently does not make much sense,
 		// but historically runc allowed it and this needs to be supported
 		// to not break compatibility.
 		//
diff --git a/utils.go b/utils.go
index 32ab33e5..2809c3df 100644
--- a/utils.go
+++ b/utils.go
@@ -53,17 +53,13 @@ func logrusToStderr() bool {
 // fatal prints the error's details if it is a libcontainer specific error type
 // then exits the program with an exit status of 1.
 func fatal(err error) {
-	fatalWithCode(err, 1)
-}
-
-func fatalWithCode(err error, ret int) {
-	// Make sure the error is written to the logger.
+	// make sure the error is written to the logger
 	logrus.Error(err)
 	if !logrusToStderr() {
 		fmt.Fprintln(os.Stderr, err)
 	}
 
-	os.Exit(ret)
+	os.Exit(1)
 }
 
 // setupSpec performs initial setup based on the cli.Context for the container
diff --git a/vendor/github.com/containerd/console/console_windows.go b/vendor/github.com/containerd/console/console_windows.go
index 787c11fe..129a9288 100644
--- a/vendor/github.com/containerd/console/console_windows.go
+++ b/vendor/github.com/containerd/console/console_windows.go
@@ -17,10 +17,10 @@
 package console
 
 import (
-	"errors"
 	"fmt"
 	"os"
 
+	"github.com/pkg/errors"
 	"golang.org/x/sys/windows"
 )
 
@@ -103,7 +103,7 @@ func (m *master) Reset() error {
 		{m.err, m.errMode},
 	} {
 		if err := windows.SetConsoleMode(s.fd, s.mode); err != nil {
-			return fmt.Errorf("unable to restore console mode: %w", err)
+			return errors.Wrap(err, "unable to restore console mode")
 		}
 	}
 
@@ -114,7 +114,7 @@ func (m *master) Size() (WinSize, error) {
 	var info windows.ConsoleScreenBufferInfo
 	err := windows.GetConsoleScreenBufferInfo(m.out, &info)
 	if err != nil {
-		return WinSize{}, fmt.Errorf("unable to get console info: %w", err)
+		return WinSize{}, errors.Wrap(err, "unable to get console info")
 	}
 
 	winsize := WinSize{
@@ -139,7 +139,7 @@ func (m *master) DisableEcho() error {
 	mode |= windows.ENABLE_LINE_INPUT
 
 	if err := windows.SetConsoleMode(m.in, mode); err != nil {
-		return fmt.Errorf("unable to set console to disable echo: %w", err)
+		return errors.Wrap(err, "unable to set console to disable echo")
 	}
 
 	return nil
@@ -192,7 +192,7 @@ func makeInputRaw(fd windows.Handle, mode uint32) error {
 	}
 
 	if err := windows.SetConsoleMode(fd, mode); err != nil {
-		return fmt.Errorf("unable to set console to raw mode: %w", err)
+		return errors.Wrap(err, "unable to set console to raw mode")
 	}
 
 	return nil
diff --git a/vendor/github.com/containerd/console/console_zos.go b/vendor/github.com/containerd/console/console_zos.go
deleted file mode 100644
index b348a839..00000000
--- a/vendor/github.com/containerd/console/console_zos.go
+++ /dev/null
@@ -1,163 +0,0 @@
-// +build zos
-
-/*
-   Copyright The containerd Authors.
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-*/
-
-package console
-
-import (
-	"fmt"
-	"os"
-
-	"golang.org/x/sys/unix"
-)
-
-// NewPty creates a new pty pair
-// The master is returned as the first console and a string
-// with the path to the pty slave is returned as the second
-func NewPty() (Console, string, error) {
-	var f File
-	var err error
-	var slave string
-	for i := 0;; i++ {
-		ptyp := fmt.Sprintf("/dev/ptyp%04d", i)
-		f, err = os.OpenFile(ptyp, os.O_RDWR, 0600)
-		if err == nil {
-			slave = fmt.Sprintf("/dev/ttyp%04d", i)
-			break
-		}
-		if os.IsNotExist(err) {
-			return nil, "", err
-		}
-		// else probably Resource Busy
-	}
-	m, err := newMaster(f)
-	if err != nil {
-		return nil, "", err
-	}
-	return m, slave, nil
-}
-
-type master struct {
-	f        File
-	original *unix.Termios
-}
-
-func (m *master) Read(b []byte) (int, error) {
-	return m.f.Read(b)
-}
-
-func (m *master) Write(b []byte) (int, error) {
-	return m.f.Write(b)
-}
-
-func (m *master) Close() error {
-	return m.f.Close()
-}
-
-func (m *master) Resize(ws WinSize) error {
-	return tcswinsz(m.f.Fd(), ws)
-}
-
-func (m *master) ResizeFrom(c Console) error {
-	ws, err := c.Size()
-	if err != nil {
-		return err
-	}
-	return m.Resize(ws)
-}
-
-func (m *master) Reset() error {
-	if m.original == nil {
-		return nil
-	}
-	return tcset(m.f.Fd(), m.original)
-}
-
-func (m *master) getCurrent() (unix.Termios, error) {
-	var termios unix.Termios
-	if err := tcget(m.f.Fd(), &termios); err != nil {
-		return unix.Termios{}, err
-	}
-	return termios, nil
-}
-
-func (m *master) SetRaw() error {
-	rawState, err := m.getCurrent()
-	if err != nil {
-		return err
-	}
-	rawState = cfmakeraw(rawState)
-	rawState.Oflag = rawState.Oflag | unix.OPOST
-	return tcset(m.f.Fd(), &rawState)
-}
-
-func (m *master) DisableEcho() error {
-	rawState, err := m.getCurrent()
-	if err != nil {
-		return err
-	}
-	rawState.Lflag = rawState.Lflag &^ unix.ECHO
-	return tcset(m.f.Fd(), &rawState)
-}
-
-func (m *master) Size() (WinSize, error) {
-	return tcgwinsz(m.f.Fd())
-}
-
-func (m *master) Fd() uintptr {
-	return m.f.Fd()
-}
-
-func (m *master) Name() string {
-	return m.f.Name()
-}
-
-// checkConsole checks if the provided file is a console
-func checkConsole(f File) error {
-	var termios unix.Termios
-	if tcget(f.Fd(), &termios) != nil {
-		return ErrNotAConsole
-	}
-	return nil
-}
-
-func newMaster(f File) (Console, error) {
-	m := &master{
-		f: f,
-	}
-	t, err := m.getCurrent()
-	if err != nil {
-		return nil, err
-	}
-	m.original = &t
-	return m, nil
-}
-
-// ClearONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
-// created by us acts normally. In particular, a not-very-well-known default of
-// Linux unix98 ptys is that they have +onlcr by default. While this isn't a
-// problem for terminal emulators, because we relay data from the terminal we
-// also relay that funky line discipline.
-func ClearONLCR(fd uintptr) error {
-	return setONLCR(fd, false)
-}
-
-// SetONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
-// created by us acts as intended for a terminal emulator.
-func SetONLCR(fd uintptr) error {
-	return setONLCR(fd, true)
-}
diff --git a/vendor/github.com/containerd/console/go.mod b/vendor/github.com/containerd/console/go.mod
index 1fe5b7fe..7fca0a9a 100644
--- a/vendor/github.com/containerd/console/go.mod
+++ b/vendor/github.com/containerd/console/go.mod
@@ -2,4 +2,7 @@ module github.com/containerd/console
 
 go 1.13
 
-require golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
+require (
+	github.com/pkg/errors v0.9.1
+	golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
+)
diff --git a/vendor/github.com/containerd/console/go.sum b/vendor/github.com/containerd/console/go.sum
index 1225630b..9db384d3 100644
--- a/vendor/github.com/containerd/console/go.sum
+++ b/vendor/github.com/containerd/console/go.sum
@@ -1,2 +1,4 @@
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/vendor/github.com/containerd/console/tc_unix.go b/vendor/github.com/containerd/console/tc_unix.go
index a6bf01e8..5cd4c550 100644
--- a/vendor/github.com/containerd/console/tc_unix.go
+++ b/vendor/github.com/containerd/console/tc_unix.go
@@ -1,4 +1,4 @@
-// +build darwin freebsd linux netbsd openbsd solaris zos
+// +build darwin freebsd linux netbsd openbsd solaris
 
 /*
    Copyright The containerd Authors.
diff --git a/vendor/github.com/containerd/console/tc_zos.go b/vendor/github.com/containerd/console/tc_zos.go
deleted file mode 100644
index 4262eaf4..00000000
--- a/vendor/github.com/containerd/console/tc_zos.go
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
-   Copyright The containerd Authors.
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-*/
-
-package console
-
-import (
-	"golang.org/x/sys/unix"
-)
-
-const (
-	cmdTcGet = unix.TCGETS
-	cmdTcSet = unix.TCSETS
-)
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/rchcon.go b/vendor/github.com/opencontainers/selinux/go-selinux/rchcon.go
deleted file mode 100644
index 897ecbac..00000000
--- a/vendor/github.com/opencontainers/selinux/go-selinux/rchcon.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// +build linux,go1.16
-
-package selinux
-
-import (
-	"errors"
-	"io/fs"
-	"os"
-
-	"github.com/opencontainers/selinux/pkg/pwalkdir"
-)
-
-func rchcon(fpath, label string) error {
-	return pwalkdir.Walk(fpath, func(p string, _ fs.DirEntry, _ error) error {
-		e := setFileLabel(p, label)
-		// Walk a file tree can race with removal, so ignore ENOENT.
-		if errors.Is(e, os.ErrNotExist) {
-			return nil
-		}
-		return e
-	})
-}
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/rchcon_go115.go b/vendor/github.com/opencontainers/selinux/go-selinux/rchcon_go115.go
deleted file mode 100644
index 2c8b033c..00000000
--- a/vendor/github.com/opencontainers/selinux/go-selinux/rchcon_go115.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// +build linux,!go1.16
-
-package selinux
-
-import (
-	"errors"
-	"os"
-
-	"github.com/opencontainers/selinux/pkg/pwalk"
-)
-
-func rchcon(fpath, label string) error {
-	return pwalk.Walk(fpath, func(p string, _ os.FileInfo, _ error) error {
-		e := setFileLabel(p, label)
-		// Walk a file tree can race with removal, so ignore ENOENT.
-		if errors.Is(e, os.ErrNotExist) {
-			return nil
-		}
-		return e
-	})
-}
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
index a804473e..62df82a3 100644
--- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
@@ -18,6 +18,7 @@ import (
 	"sync"
 
 	"github.com/bits-and-blooms/bitset"
+	"github.com/opencontainers/selinux/pkg/pwalk"
 	"golang.org/x/sys/unix"
 )
 
@@ -1047,10 +1048,17 @@ func chcon(fpath string, label string, recurse bool) error {
 	}
 
 	if !recurse {
-		return setFileLabel(fpath, label)
+		return SetFileLabel(fpath, label)
 	}
 
-	return rchcon(fpath, label)
+	return pwalk.Walk(fpath, func(p string, info os.FileInfo, err error) error {
+		e := SetFileLabel(p, label)
+		// Walk a file tree can race with removal, so ignore ENOENT
+		if errors.Is(e, os.ErrNotExist) {
+			return nil
+		}
+		return e
+	})
 }
 
 // dupSecOpt takes an SELinux process label and returns security options that
diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalk/README.md b/vendor/github.com/opencontainers/selinux/pkg/pwalk/README.md
index 7e78dce0..16c4dfd3 100644
--- a/vendor/github.com/opencontainers/selinux/pkg/pwalk/README.md
+++ b/vendor/github.com/opencontainers/selinux/pkg/pwalk/README.md
@@ -8,12 +8,6 @@ By default, it utilizes 2\*runtime.NumCPU() goroutines for callbacks.
 This can be changed by using WalkN function which has the additional
 parameter, specifying the number of goroutines (concurrency).
 
-### pwalk vs pwalkdir
-
-This package is deprecated in favor of
-[pwalkdir](https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir),
-which is faster, but requires at least Go 1.16.
-
 ### Caveats
 
 Please note the following limitations of this code:
diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go b/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go
index 011fe862..a8088a19 100644
--- a/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go
+++ b/vendor/github.com/opencontainers/selinux/pkg/pwalk/pwalk.go
@@ -19,7 +19,7 @@ type WalkFunc = filepath.WalkFunc
 //
 // Note that this implementation only supports primitive error handling:
 //
-// - no errors are ever passed to walkFn;
+// - no errors are ever passed to WalkFn;
 //
 // - once a walkFn returns any error, all further processing stops
 // and the error is returned to the caller of Walk;
@@ -95,7 +95,7 @@ func WalkN(root string, walkFn WalkFunc, num int) error {
 	return err
 }
 
-// walkArgs holds the arguments that were passed to the Walk or WalkN
+// walkArgs holds the arguments that were passed to the Walk or WalkLimit
 // functions.
 type walkArgs struct {
 	path string
diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/README.md b/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/README.md
deleted file mode 100644
index 068ac400..00000000
--- a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/README.md
+++ /dev/null
@@ -1,54 +0,0 @@
-## pwalkdir: parallel implementation of filepath.WalkDir
-
-This is a wrapper for [filepath.WalkDir](https://pkg.go.dev/path/filepath#WalkDir)
-which may speed it up by calling multiple callback functions (WalkDirFunc)
-in parallel, utilizing goroutines.
-
-By default, it utilizes 2\*runtime.NumCPU() goroutines for callbacks.
-This can be changed by using WalkN function which has the additional
-parameter, specifying the number of goroutines (concurrency).
-
-### pwalk vs pwalkdir
-
-This package is very similar to
-[pwalk](https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir),
-but utilizes `filepath.WalkDir` (added to Go 1.16), which does not call stat(2)
-on every entry and is therefore faster (up to 3x, depending on usage scenario).
-
-Users who are OK with requiring Go 1.16+ should switch to this
-implementation.
-
-### Caveats
-
-Please note the following limitations of this code:
-
-* Unlike filepath.WalkDir, the order of calls is non-deterministic;
-
-* Only primitive error handling is supported:
-
-  * fs.SkipDir is not supported;
-
-  * no errors are ever passed to WalkDirFunc;
-
-  * once any error is returned from any walkDirFunc instance, no more calls
-    to WalkDirFunc are made, and the error is returned to the caller of WalkDir;
-
-  * if more than one WalkDirFunc instance will return an error, only one
-    of such errors will be propagated to and returned by WalkDir, others
-    will be silently discarded.
-
-### Documentation
-
-For the official documentation, see
-https://pkg.go.dev/github.com/opencontainers/selinux/pkg/pwalkdir
-
-### Benchmarks
-
-For a WalkDirFunc that consists solely of the return statement, this
-implementation is about 15% slower than the standard library's
-filepath.WalkDir.
-
-Otherwise (if a WalkDirFunc is actually doing something) this is usually
-faster, except when the WalkDirN(..., 1) is used. Run `go test -bench .`
-to see how different operations can benefit from it, as well as how the
-level of paralellism affects the speed.
diff --git a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go b/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go
deleted file mode 100644
index 22282075..00000000
--- a/vendor/github.com/opencontainers/selinux/pkg/pwalkdir/pwalkdir.go
+++ /dev/null
@@ -1,103 +0,0 @@
-// +build go1.16
-
-package pwalkdir
-
-import (
-	"fmt"
-	"io/fs"
-	"path/filepath"
-	"runtime"
-	"sync"
-)
-
-// Walk is a wrapper for filepath.WalkDir which can call multiple walkFn
-// in parallel, allowing to handle each item concurrently. A maximum of
-// twice the runtime.NumCPU() walkFn will be called at any one time.
-// If you want to change the maximum, use WalkN instead.
-//
-// The order of calls is non-deterministic.
-//
-// Note that this implementation only supports primitive error handling:
-//
-// - no errors are ever passed to walkFn;
-//
-// - once a walkFn returns any error, all further processing stops
-// and the error is returned to the caller of Walk;
-//
-// - filepath.SkipDir is not supported;
-//
-// - if more than one walkFn instance will return an error, only one
-// of such errors will be propagated and returned by Walk, others
-// will be silently discarded.
-func Walk(root string, walkFn fs.WalkDirFunc) error {
-	return WalkN(root, walkFn, runtime.NumCPU()*2)
-}
-
-// WalkN is a wrapper for filepath.WalkDir which can call multiple walkFn
-// in parallel, allowing to handle each item concurrently. A maximum of
-// num walkFn will be called at any one time.
-//
-// Please see Walk documentation for caveats of using this function.
-func WalkN(root string, walkFn fs.WalkDirFunc, num int) error {
-	// make sure limit is sensible
-	if num < 1 {
-		return fmt.Errorf("walk(%q): num must be > 0", root)
-	}
-
-	files := make(chan *walkArgs, 2*num)
-	errCh := make(chan error, 1) // Get the first error, ignore others.
-
-	// Start walking a tree asap.
-	var (
-		err error
-		wg  sync.WaitGroup
-	)
-	wg.Add(1)
-	go func() {
-		err = filepath.WalkDir(root, func(p string, entry fs.DirEntry, err error) error {
-			if err != nil {
-				close(files)
-				return err
-			}
-			// Add a file to the queue unless a callback sent an error.
-			select {
-			case e := <-errCh:
-				close(files)
-				return e
-			default:
-				files <- &walkArgs{path: p, entry: entry}
-				return nil
-			}
-		})
-		if err == nil {
-			close(files)
-		}
-		wg.Done()
-	}()
-
-	wg.Add(num)
-	for i := 0; i < num; i++ {
-		go func() {
-			for file := range files {
-				if e := walkFn(file.path, file.entry, nil); e != nil {
-					select {
-					case errCh <- e: // sent ok
-					default: // buffer full
-					}
-				}
-			}
-			wg.Done()
-		}()
-	}
-
-	wg.Wait()
-
-	return err
-}
-
-// walkArgs holds the arguments that were passed to the Walk or WalkN
-// functions.
-type walkArgs struct {
-	path  string
-	entry fs.DirEntry
-}
diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore
new file mode 100644
index 00000000..daf913b1
--- /dev/null
+++ b/vendor/github.com/pkg/errors/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml
new file mode 100644
index 00000000..9159de03
--- /dev/null
+++ b/vendor/github.com/pkg/errors/.travis.yml
@@ -0,0 +1,10 @@
+language: go
+go_import_path: github.com/pkg/errors
+go:
+  - 1.11.x
+  - 1.12.x
+  - 1.13.x
+  - tip
+
+script:
+  - make check
diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE
new file mode 100644
index 00000000..835ba3e7
--- /dev/null
+++ b/vendor/github.com/pkg/errors/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2015, Dave Cheney <dave@cheney.net>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/pkg/errors/Makefile b/vendor/github.com/pkg/errors/Makefile
new file mode 100644
index 00000000..ce9d7cde
--- /dev/null
+++ b/vendor/github.com/pkg/errors/Makefile
@@ -0,0 +1,44 @@
+PKGS := github.com/pkg/errors
+SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS))
+GO := go
+
+check: test vet gofmt misspell unconvert staticcheck ineffassign unparam
+
+test: 
+	$(GO) test $(PKGS)
+
+vet: | test
+	$(GO) vet $(PKGS)
+
+staticcheck:
+	$(GO) get honnef.co/go/tools/cmd/staticcheck
+	staticcheck -checks all $(PKGS)
+
+misspell:
+	$(GO) get github.com/client9/misspell/cmd/misspell
+	misspell \
+		-locale GB \
+		-error \
+		*.md *.go
+
+unconvert:
+	$(GO) get github.com/mdempsky/unconvert
+	unconvert -v $(PKGS)
+
+ineffassign:
+	$(GO) get github.com/gordonklaus/ineffassign
+	find $(SRCDIRS) -name '*.go' | xargs ineffassign
+
+pedantic: check errcheck
+
+unparam:
+	$(GO) get mvdan.cc/unparam
+	unparam ./...
+
+errcheck:
+	$(GO) get github.com/kisielk/errcheck
+	errcheck $(PKGS)
+
+gofmt:  
+	@echo Checking code is gofmted
+	@test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)"
diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md
new file mode 100644
index 00000000..54dfdcb1
--- /dev/null
+++ b/vendor/github.com/pkg/errors/README.md
@@ -0,0 +1,59 @@
+# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
+
+Package errors provides simple error handling primitives.
+
+`go get github.com/pkg/errors`
+
+The traditional error handling idiom in Go is roughly akin to
+```go
+if err != nil {
+        return err
+}
+```
+which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
+
+## Adding context to an error
+
+The errors.Wrap function returns a new error that adds context to the original error. For example
+```go
+_, err := ioutil.ReadAll(r)
+if err != nil {
+        return errors.Wrap(err, "read failed")
+}
+```
+## Retrieving the cause of an error
+
+Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
+```go
+type causer interface {
+        Cause() error
+}
+```
+`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
+```go
+switch err := errors.Cause(err).(type) {
+case *MyError:
+        // handle specifically
+default:
+        // unknown error
+}
+```
+
+[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
+
+## Roadmap
+
+With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:
+
+- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible)
+- 1.0. Final release.
+
+## Contributing
+
+Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. 
+
+Before sending a PR, please discuss your change by raising an issue.
+
+## License
+
+BSD-2-Clause
diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml
new file mode 100644
index 00000000..a932eade
--- /dev/null
+++ b/vendor/github.com/pkg/errors/appveyor.yml
@@ -0,0 +1,32 @@
+version: build-{build}.{branch}
+
+clone_folder: C:\gopath\src\github.com\pkg\errors
+shallow_clone: true # for startup speed
+
+environment:
+  GOPATH: C:\gopath
+
+platform:
+  - x64
+
+# http://www.appveyor.com/docs/installed-software
+install:
+  # some helpful output for debugging builds
+  - go version
+  - go env
+  # pre-installed MinGW at C:\MinGW is 32bit only
+  # but MSYS2 at C:\msys64 has mingw64
+  - set PATH=C:\msys64\mingw64\bin;%PATH%
+  - gcc --version
+  - g++ --version
+
+build_script:
+  - go install -v ./...
+
+test_script:
+  - set PATH=C:\gopath\bin;%PATH%
+  - go test -v ./...
+
+#artifacts:
+#  - path: '%GOPATH%\bin\*.exe'
+deploy: off
diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go
new file mode 100644
index 00000000..161aea25
--- /dev/null
+++ b/vendor/github.com/pkg/errors/errors.go
@@ -0,0 +1,288 @@
+// Package errors provides simple error handling primitives.
+//
+// The traditional error handling idiom in Go is roughly akin to
+//
+//     if err != nil {
+//             return err
+//     }
+//
+// which when applied recursively up the call stack results in error reports
+// without context or debugging information. The errors package allows
+// programmers to add context to the failure path in their code in a way
+// that does not destroy the original value of the error.
+//
+// Adding context to an error
+//
+// The errors.Wrap function returns a new error that adds context to the
+// original error by recording a stack trace at the point Wrap is called,
+// together with the supplied message. For example
+//
+//     _, err := ioutil.ReadAll(r)
+//     if err != nil {
+//             return errors.Wrap(err, "read failed")
+//     }
+//
+// If additional control is required, the errors.WithStack and
+// errors.WithMessage functions destructure errors.Wrap into its component
+// operations: annotating an error with a stack trace and with a message,
+// respectively.
+//
+// Retrieving the cause of an error
+//
+// Using errors.Wrap constructs a stack of errors, adding context to the
+// preceding error. Depending on the nature of the error it may be necessary
+// to reverse the operation of errors.Wrap to retrieve the original error
+// for inspection. Any error value which implements this interface
+//
+//     type causer interface {
+//             Cause() error
+//     }
+//
+// can be inspected by errors.Cause. errors.Cause will recursively retrieve
+// the topmost error that does not implement causer, which is assumed to be
+// the original cause. For example:
+//
+//     switch err := errors.Cause(err).(type) {
+//     case *MyError:
+//             // handle specifically
+//     default:
+//             // unknown error
+//     }
+//
+// Although the causer interface is not exported by this package, it is
+// considered a part of its stable public interface.
+//
+// Formatted printing of errors
+//
+// All error values returned from this package implement fmt.Formatter and can
+// be formatted by the fmt package. The following verbs are supported:
+//
+//     %s    print the error. If the error has a Cause it will be
+//           printed recursively.
+//     %v    see %s
+//     %+v   extended format. Each Frame of the error's StackTrace will
+//           be printed in detail.
+//
+// Retrieving the stack trace of an error or wrapper
+//
+// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
+// invoked. This information can be retrieved with the following interface:
+//
+//     type stackTracer interface {
+//             StackTrace() errors.StackTrace
+//     }
+//
+// The returned errors.StackTrace type is defined as
+//
+//     type StackTrace []Frame
+//
+// The Frame type represents a call site in the stack trace. Frame supports
+// the fmt.Formatter interface that can be used for printing information about
+// the stack trace of this error. For example:
+//
+//     if err, ok := err.(stackTracer); ok {
+//             for _, f := range err.StackTrace() {
+//                     fmt.Printf("%+s:%d\n", f, f)
+//             }
+//     }
+//
+// Although the stackTracer interface is not exported by this package, it is
+// considered a part of its stable public interface.
+//
+// See the documentation for Frame.Format for more details.
+package errors
+
+import (
+	"fmt"
+	"io"
+)
+
+// New returns an error with the supplied message.
+// New also records the stack trace at the point it was called.
+func New(message string) error {
+	return &fundamental{
+		msg:   message,
+		stack: callers(),
+	}
+}
+
+// Errorf formats according to a format specifier and returns the string
+// as a value that satisfies error.
+// Errorf also records the stack trace at the point it was called.
+func Errorf(format string, args ...interface{}) error {
+	return &fundamental{
+		msg:   fmt.Sprintf(format, args...),
+		stack: callers(),
+	}
+}
+
+// fundamental is an error that has a message and a stack, but no caller.
+type fundamental struct {
+	msg string
+	*stack
+}
+
+func (f *fundamental) Error() string { return f.msg }
+
+func (f *fundamental) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			io.WriteString(s, f.msg)
+			f.stack.Format(s, verb)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, f.msg)
+	case 'q':
+		fmt.Fprintf(s, "%q", f.msg)
+	}
+}
+
+// WithStack annotates err with a stack trace at the point WithStack was called.
+// If err is nil, WithStack returns nil.
+func WithStack(err error) error {
+	if err == nil {
+		return nil
+	}
+	return &withStack{
+		err,
+		callers(),
+	}
+}
+
+type withStack struct {
+	error
+	*stack
+}
+
+func (w *withStack) Cause() error { return w.error }
+
+// Unwrap provides compatibility for Go 1.13 error chains.
+func (w *withStack) Unwrap() error { return w.error }
+
+func (w *withStack) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Cause())
+			w.stack.Format(s, verb)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+// Wrap returns an error annotating err with a stack trace
+// at the point Wrap is called, and the supplied message.
+// If err is nil, Wrap returns nil.
+func Wrap(err error, message string) error {
+	if err == nil {
+		return nil
+	}
+	err = &withMessage{
+		cause: err,
+		msg:   message,
+	}
+	return &withStack{
+		err,
+		callers(),
+	}
+}
+
+// Wrapf returns an error annotating err with a stack trace
+// at the point Wrapf is called, and the format specifier.
+// If err is nil, Wrapf returns nil.
+func Wrapf(err error, format string, args ...interface{}) error {
+	if err == nil {
+		return nil
+	}
+	err = &withMessage{
+		cause: err,
+		msg:   fmt.Sprintf(format, args...),
+	}
+	return &withStack{
+		err,
+		callers(),
+	}
+}
+
+// WithMessage annotates err with a new message.
+// If err is nil, WithMessage returns nil.
+func WithMessage(err error, message string) error {
+	if err == nil {
+		return nil
+	}
+	return &withMessage{
+		cause: err,
+		msg:   message,
+	}
+}
+
+// WithMessagef annotates err with the format specifier.
+// If err is nil, WithMessagef returns nil.
+func WithMessagef(err error, format string, args ...interface{}) error {
+	if err == nil {
+		return nil
+	}
+	return &withMessage{
+		cause: err,
+		msg:   fmt.Sprintf(format, args...),
+	}
+}
+
+type withMessage struct {
+	cause error
+	msg   string
+}
+
+func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
+func (w *withMessage) Cause() error  { return w.cause }
+
+// Unwrap provides compatibility for Go 1.13 error chains.
+func (w *withMessage) Unwrap() error { return w.cause }
+
+func (w *withMessage) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v\n", w.Cause())
+			io.WriteString(s, w.msg)
+			return
+		}
+		fallthrough
+	case 's', 'q':
+		io.WriteString(s, w.Error())
+	}
+}
+
+// Cause returns the underlying cause of the error, if possible.
+// An error value has a cause if it implements the following
+// interface:
+//
+//     type causer interface {
+//            Cause() error
+//     }
+//
+// If the error does not implement Cause, the original error will
+// be returned. If the error is nil, nil will be returned without further
+// investigation.
+func Cause(err error) error {
+	type causer interface {
+		Cause() error
+	}
+
+	for err != nil {
+		cause, ok := err.(causer)
+		if !ok {
+			break
+		}
+		err = cause.Cause()
+	}
+	return err
+}
diff --git a/vendor/github.com/pkg/errors/go113.go b/vendor/github.com/pkg/errors/go113.go
new file mode 100644
index 00000000..be0d10d0
--- /dev/null
+++ b/vendor/github.com/pkg/errors/go113.go
@@ -0,0 +1,38 @@
+// +build go1.13
+
+package errors
+
+import (
+	stderrors "errors"
+)
+
+// Is reports whether any error in err's chain matches target.
+//
+// The chain consists of err itself followed by the sequence of errors obtained by
+// repeatedly calling Unwrap.
+//
+// An error is considered to match a target if it is equal to that target or if
+// it implements a method Is(error) bool such that Is(target) returns true.
+func Is(err, target error) bool { return stderrors.Is(err, target) }
+
+// As finds the first error in err's chain that matches target, and if so, sets
+// target to that error value and returns true.
+//
+// The chain consists of err itself followed by the sequence of errors obtained by
+// repeatedly calling Unwrap.
+//
+// An error matches target if the error's concrete value is assignable to the value
+// pointed to by target, or if the error has a method As(interface{}) bool such that
+// As(target) returns true. In the latter case, the As method is responsible for
+// setting target.
+//
+// As will panic if target is not a non-nil pointer to either a type that implements
+// error, or to any interface type. As returns false if err is nil.
+func As(err error, target interface{}) bool { return stderrors.As(err, target) }
+
+// Unwrap returns the result of calling the Unwrap method on err, if err's
+// type contains an Unwrap method returning error.
+// Otherwise, Unwrap returns nil.
+func Unwrap(err error) error {
+	return stderrors.Unwrap(err)
+}
diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go
new file mode 100644
index 00000000..779a8348
--- /dev/null
+++ b/vendor/github.com/pkg/errors/stack.go
@@ -0,0 +1,177 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+	"path"
+	"runtime"
+	"strconv"
+	"strings"
+)
+
+// Frame represents a program counter inside a stack frame.
+// For historical reasons if Frame is interpreted as a uintptr
+// its value represents the program counter + 1.
+type Frame uintptr
+
+// pc returns the program counter for this frame;
+// multiple frames may have the same PC value.
+func (f Frame) pc() uintptr { return uintptr(f) - 1 }
+
+// file returns the full path to the file that contains the
+// function for this Frame's pc.
+func (f Frame) file() string {
+	fn := runtime.FuncForPC(f.pc())
+	if fn == nil {
+		return "unknown"
+	}
+	file, _ := fn.FileLine(f.pc())
+	return file
+}
+
+// line returns the line number of source code of the
+// function for this Frame's pc.
+func (f Frame) line() int {
+	fn := runtime.FuncForPC(f.pc())
+	if fn == nil {
+		return 0
+	}
+	_, line := fn.FileLine(f.pc())
+	return line
+}
+
+// name returns the name of this function, if known.
+func (f Frame) name() string {
+	fn := runtime.FuncForPC(f.pc())
+	if fn == nil {
+		return "unknown"
+	}
+	return fn.Name()
+}
+
+// Format formats the frame according to the fmt.Formatter interface.
+//
+//    %s    source file
+//    %d    source line
+//    %n    function name
+//    %v    equivalent to %s:%d
+//
+// Format accepts flags that alter the printing of some verbs, as follows:
+//
+//    %+s   function name and path of source file relative to the compile time
+//          GOPATH separated by \n\t (<funcname>\n\t<path>)
+//    %+v   equivalent to %+s:%d
+func (f Frame) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 's':
+		switch {
+		case s.Flag('+'):
+			io.WriteString(s, f.name())
+			io.WriteString(s, "\n\t")
+			io.WriteString(s, f.file())
+		default:
+			io.WriteString(s, path.Base(f.file()))
+		}
+	case 'd':
+		io.WriteString(s, strconv.Itoa(f.line()))
+	case 'n':
+		io.WriteString(s, funcname(f.name()))
+	case 'v':
+		f.Format(s, 's')
+		io.WriteString(s, ":")
+		f.Format(s, 'd')
+	}
+}
+
+// MarshalText formats a stacktrace Frame as a text string. The output is the
+// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
+func (f Frame) MarshalText() ([]byte, error) {
+	name := f.name()
+	if name == "unknown" {
+		return []byte(name), nil
+	}
+	return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
+}
+
+// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
+type StackTrace []Frame
+
+// Format formats the stack of Frames according to the fmt.Formatter interface.
+//
+//    %s	lists source files for each Frame in the stack
+//    %v	lists the source file and line number for each Frame in the stack
+//
+// Format accepts flags that alter the printing of some verbs, as follows:
+//
+//    %+v   Prints filename, function, and line number for each Frame in the stack.
+func (st StackTrace) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		switch {
+		case s.Flag('+'):
+			for _, f := range st {
+				io.WriteString(s, "\n")
+				f.Format(s, verb)
+			}
+		case s.Flag('#'):
+			fmt.Fprintf(s, "%#v", []Frame(st))
+		default:
+			st.formatSlice(s, verb)
+		}
+	case 's':
+		st.formatSlice(s, verb)
+	}
+}
+
+// formatSlice will format this StackTrace into the given buffer as a slice of
+// Frame, only valid when called with '%s' or '%v'.
+func (st StackTrace) formatSlice(s fmt.State, verb rune) {
+	io.WriteString(s, "[")
+	for i, f := range st {
+		if i > 0 {
+			io.WriteString(s, " ")
+		}
+		f.Format(s, verb)
+	}
+	io.WriteString(s, "]")
+}
+
+// stack represents a stack of program counters.
+type stack []uintptr
+
+func (s *stack) Format(st fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		switch {
+		case st.Flag('+'):
+			for _, pc := range *s {
+				f := Frame(pc)
+				fmt.Fprintf(st, "\n%+v", f)
+			}
+		}
+	}
+}
+
+func (s *stack) StackTrace() StackTrace {
+	f := make([]Frame, len(*s))
+	for i := 0; i < len(f); i++ {
+		f[i] = Frame((*s)[i])
+	}
+	return f
+}
+
+func callers() *stack {
+	const depth = 32
+	var pcs [depth]uintptr
+	n := runtime.Callers(3, pcs[:])
+	var st stack = pcs[0:n]
+	return &st
+}
+
+// funcname removes the path prefix component of a function's name reported by func.Name().
+func funcname(name string) string {
+	i := strings.LastIndex(name, "/")
+	name = name[i+1:]
+	i = strings.Index(name, ".")
+	return name[i+1:]
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 24b3c52f..5c77cf79 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -13,7 +13,7 @@ github.com/cilium/ebpf/internal
 github.com/cilium/ebpf/internal/btf
 github.com/cilium/ebpf/internal/unix
 github.com/cilium/ebpf/link
-# github.com/containerd/console v1.0.3
+# github.com/containerd/console v1.0.2
 ## explicit
 github.com/containerd/console
 # github.com/coreos/go-systemd/v22 v22.3.2
@@ -42,12 +42,13 @@ github.com/mrunalp/fileutils
 # github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
 ## explicit
 github.com/opencontainers/runtime-spec/specs-go
-# github.com/opencontainers/selinux v1.8.4
+# github.com/opencontainers/selinux v1.8.3
 ## explicit
 github.com/opencontainers/selinux/go-selinux
 github.com/opencontainers/selinux/go-selinux/label
 github.com/opencontainers/selinux/pkg/pwalk
-github.com/opencontainers/selinux/pkg/pwalkdir
+# github.com/pkg/errors v0.9.1
+github.com/pkg/errors
 # github.com/russross/blackfriday/v2 v2.0.1
 github.com/russross/blackfriday/v2
 # github.com/seccomp/libseccomp-golang v0.9.1


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 591a1870..2831aeb6 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -17,46 +17,22 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        go-version: [1.15.x, 1.16.x, 1.17.x]
+        go-version: [1.15.x, 1.16.x]
         rootless: ["rootless", ""]
         race: ["-race", ""]
-        criu: [""]
-        include:
-          # Also test against latest criu-dev
-          - go-version: 1.16.x
-            rootless: ""
-            race: ""
-            criu: "criu-dev"
 
     steps:
 
     - name: checkout
       uses: actions/checkout@v2
 
-    - name: Dump matrix context
-      env:
-        MATRIX_CONTEXT: ${{ toJSON(matrix) }}
-      run: echo "$MATRIX_CONTEXT"
-
     - name: install deps
-      if: matrix.criu == ''
       run: |
         # criu repo
         sudo add-apt-repository -y ppa:criu/ppa
         # apt-add-repository runs apt update so we don't have to
         sudo apt -q install libseccomp-dev criu
 
-    - name: install deps (criu ${{ matrix.criu }})
-      if: matrix.criu != ''
-      run: |
-        sudo apt -q update
-        sudo apt -q install libseccomp-dev \
-          libcap-dev libnet1-dev libnl-3-dev \
-          libprotobuf-c-dev libprotobuf-dev protobuf-c-compiler protobuf-compiler
-        git clone https://github.com/checkpoint-restore/criu.git ~/criu
-        (cd ~/criu && git checkout ${{ matrix.criu }} && sudo make install-criu)
-        rm -rf ~/criu
-
     - name: install go ${{ matrix.go-version }}
       uses: actions/setup-go@v2
       with:
diff --git a/libcontainer/cgroups/fs/stats_util_test.go b/libcontainer/cgroups/fs/stats_util_test.go
index 757e65b6..29bd01d3 100644
--- a/libcontainer/cgroups/fs/stats_util_test.go
+++ b/libcontainer/cgroups/fs/stats_util_test.go
@@ -51,7 +51,7 @@ func expectBlkioStatsEquals(t *testing.T, expected, actual cgroups.BlkioStats) {
 	}
 
 	if err := blkioStatEntryEquals(expected.IoMergedRecursive, actual.IoMergedRecursive); err != nil {
-		t.Errorf("blkio IoMergedRecursive do not match: expected: %v, actual: %v", expected.IoMergedRecursive, actual.IoMergedRecursive)
+		t.Errorf("blkio IoMergedRecursive do not match: expcted: %v, actual: %v", expected.IoMergedRecursive, actual.IoMergedRecursive)
 	}
 
 	if err := blkioStatEntryEquals(expected.IoTimeRecursive, actual.IoTimeRecursive); err != nil {
diff --git a/libcontainer/cgroups/getallpids_test.go b/libcontainer/cgroups/getallpids_test.go
deleted file mode 100644
index b6100686..00000000
--- a/libcontainer/cgroups/getallpids_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// +build linux
-
-package cgroups
-
-import (
-	"testing"
-)
-
-func BenchmarkGetAllPids(b *testing.B) {
-	total := 0
-	for i := 0; i < b.N; i++ {
-		i, err := GetAllPids("/sys/fs/cgroup")
-		if err != nil {
-			b.Fatal(err)
-		}
-		total += len(i)
-	}
-	b.Logf("iter: %d, total: %d", b.N, total)
-}
diff --git a/libcontainer/cgroups/systemd/systemd_test.go b/libcontainer/cgroups/systemd/systemd_test.go
index 6976c4ca..55685d05 100644
--- a/libcontainer/cgroups/systemd/systemd_test.go
+++ b/libcontainer/cgroups/systemd/systemd_test.go
@@ -40,23 +40,6 @@ func TestSystemdVersion(t *testing.T) {
 	}
 }
 
-func TestValidUnitTypes(t *testing.T) {
-	testCases := []struct {
-		unitName         string
-		expectedUnitType string
-	}{
-		{"system.slice", "Slice"},
-		{"kubepods.slice", "Slice"},
-		{"testing-container:ab.scope", "Scope"},
-	}
-	for _, sdTest := range testCases {
-		unitType := getUnitType(sdTest.unitName)
-		if unitType != sdTest.expectedUnitType {
-			t.Errorf("getUnitType(%s); want %q; got %q", sdTest.unitName, sdTest.expectedUnitType, unitType)
-		}
-	}
-}
-
 func newManager(config *configs.Cgroup) cgroups.Manager {
 	if cgroups.IsCgroup2UnifiedMode() {
 		return NewUnifiedManager(config, "", false)
@@ -358,7 +341,7 @@ func TestFreezePodCgroup(t *testing.T) {
 	containerConfig := &configs.Cgroup{
 		Parent:      "system-runc_test_pod.slice",
 		ScopePrefix: "test",
-		Name:        "inner-container",
+		Name:        "inner-contianer",
 		Resources:   &configs.Resources{},
 	}
 
diff --git a/libcontainer/cgroups/systemd/v1_test.go b/libcontainer/cgroups/systemd/v1_test.go
deleted file mode 100644
index 25ac5714..00000000
--- a/libcontainer/cgroups/systemd/v1_test.go
+++ /dev/null
@@ -1,217 +0,0 @@
-package systemd
-
-import (
-	"os"
-	"os/exec"
-	"strings"
-	"testing"
-
-	"golang.org/x/sys/unix"
-
-	"github.com/opencontainers/runc/libcontainer/cgroups"
-	"github.com/opencontainers/runc/libcontainer/configs"
-)
-
-func TestFreezeBeforeSet(t *testing.T) {
-	requireV1(t)
-
-	testCases := []struct {
-		desc string
-		// Test input.
-		cg        *configs.Cgroup
-		preFreeze bool
-		// Expected values.
-		// Before unit creation (Apply).
-		freeze0, thaw0 bool
-		// After unit creation.
-		freeze1, thaw1 bool
-	}{
-		{
-			// A slice with SkipDevices.
-			desc: "slice,skip-devices",
-			cg: &configs.Cgroup{
-				Name:   "system-runc_test_freeze_1.slice",
-				Parent: "system.slice",
-				Resources: &configs.Resources{
-					SkipDevices: true,
-				},
-			},
-			// Expected.
-			freeze0: false,
-			thaw0:   false,
-			freeze1: false,
-			thaw1:   false,
-		},
-		{
-			// A scope with SkipDevices. Not a realistic scenario with runc
-			// (as container can't have SkipDevices == true), but possible
-			// for a standalone cgroup manager.
-			desc: "scope,skip-devices",
-			cg: &configs.Cgroup{
-				ScopePrefix: "test",
-				Name:        "testFreeze2",
-				Parent:      "system.slice",
-				Resources: &configs.Resources{
-					SkipDevices: true,
-				},
-			},
-			// Expected.
-			freeze0: false,
-			thaw0:   false,
-			freeze1: false,
-			thaw1:   false,
-		},
-		{
-			// A slice that is about to be frozen in Set.
-			desc: "slice,will-freeze",
-			cg: &configs.Cgroup{
-				Name:   "system-runc_test_freeze_3.slice",
-				Parent: "system.slice",
-				Resources: &configs.Resources{
-					Freezer: configs.Frozen,
-				},
-			},
-			// Expected.
-			freeze0: true,
-			thaw0:   false,
-			freeze1: true,
-			thaw1:   false,
-		},
-		{
-			// A pre-frozen slice that should stay frozen.
-			desc: "slice,pre-frozen,will-freeze",
-			cg: &configs.Cgroup{
-				Name:   "system-runc_test_freeze_4.slice",
-				Parent: "system.slice",
-				Resources: &configs.Resources{
-					Freezer: configs.Frozen,
-				},
-			},
-			preFreeze: true,
-			// Expected.
-			freeze0: true, // not actually frozen yet.
-			thaw0:   false,
-			freeze1: false,
-			thaw1:   false,
-		},
-		{
-			// A pre-frozen scope with skip devices set.
-			desc: "scope,pre-frozen,skip-devices",
-			cg: &configs.Cgroup{
-				ScopePrefix: "test",
-				Name:        "testFreeze5",
-				Parent:      "system.slice",
-				Resources: &configs.Resources{
-					SkipDevices: true,
-				},
-			},
-			preFreeze: true,
-			// Expected.
-			freeze0: false,
-			thaw0:   false,
-			freeze1: false,
-			thaw1:   false,
-		},
-		{
-			// A pre-frozen scope which will be thawed.
-			desc: "scope,pre-frozen",
-			cg: &configs.Cgroup{
-				ScopePrefix: "test",
-				Name:        "testFreeze6",
-				Parent:      "system.slice",
-				Resources:   &configs.Resources{},
-			},
-			preFreeze: true,
-			// Expected.
-			freeze0: true, // not actually frozen yet.
-			thaw0:   true,
-			freeze1: false,
-			thaw1:   false,
-		},
-	}
-
-	for _, tc := range testCases {
-		tc := tc
-		t.Run(tc.desc, func(t *testing.T) {
-			m := NewLegacyManager(tc.cg, nil)
-			defer m.Destroy() //nolint:errcheck
-			lm := m.(*legacyManager)
-
-			// Checks for a non-existent unit.
-			freeze, thaw, err := lm.freezeBeforeSet(getUnitName(tc.cg), tc.cg.Resources)
-			if err != nil {
-				t.Fatal(err)
-			}
-			if freeze != tc.freeze0 || thaw != tc.thaw0 {
-				t.Errorf("before Apply (non-existent unit): expected freeze: %v, thaw: %v, got freeze: %v, thaw: %v",
-					tc.freeze0, tc.thaw0, freeze, thaw)
-			}
-
-			// Create systemd unit.
-			pid := -1
-			if strings.HasSuffix(getUnitName(tc.cg), ".scope") {
-				// Scopes require a process inside.
-				cmd := exec.Command("bash", "-c", "sleep 1m")
-				if err := cmd.Start(); err != nil {
-					t.Fatal(err)
-				}
-				pid = cmd.Process.Pid
-				// Make sure to not leave a zombie.
-				defer func() {
-					// These may fail, we don't care.
-					_ = cmd.Process.Kill()
-					_ = cmd.Wait()
-				}()
-			}
-			if err := m.Apply(pid); err != nil {
-				t.Fatal(err)
-			}
-			if tc.preFreeze {
-				if err := m.Freeze(configs.Frozen); err != nil {
-					t.Error(err)
-					return // no more checks
-				}
-			}
-			freeze, thaw, err = lm.freezeBeforeSet(getUnitName(tc.cg), tc.cg.Resources)
-			if err != nil {
-				t.Error(err)
-				return // no more checks
-			}
-			if freeze != tc.freeze1 || thaw != tc.thaw1 {
-				t.Errorf("expected freeze: %v, thaw: %v, got freeze: %v, thaw: %v",
-					tc.freeze1, tc.thaw1, freeze, thaw)
-			}
-			// Destroy() timeouts on a frozen container, so we need to thaw it.
-			if tc.preFreeze {
-				if err := m.Freeze(configs.Thawed); err != nil {
-					t.Error(err)
-				}
-			}
-			// Destroy() does not kill processes in cgroup, so we should.
-			if pid != -1 {
-				if err = unix.Kill(pid, unix.SIGKILL); err != nil {
-					t.Errorf("unable to kill pid %d: %s", pid, err)
-				}
-			}
-			// Not really needed, but may help catch some bugs.
-			if err := m.Destroy(); err != nil {
-				t.Errorf("destroy: %s", err)
-			}
-		})
-	}
-}
-
-// requireV1 skips the test unless a set of requirements (cgroup v1,
-// systemd, root) is met.
-func requireV1(t *testing.T) {
-	t.Helper()
-	if cgroups.IsCgroup2UnifiedMode() {
-		t.Skip("Test requires cgroup v1.")
-	}
-	if !IsRunningSystemd() {
-		t.Skip("Test requires systemd.")
-	}
-	if os.Geteuid() != 0 {
-		t.Skip("Test requires root.")
-	}
-}
diff --git a/libcontainer/nsenter/nsenter_test.go b/libcontainer/nsenter/nsenter_test.go
index 9201f026..9627a61f 100644
--- a/libcontainer/nsenter/nsenter_test.go
+++ b/libcontainer/nsenter/nsenter_test.go
@@ -224,7 +224,7 @@ func TestNsenterChildLogging(t *testing.T) {
 		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)
+		t.Fatalf("child log: empty log fileds: level=\"%s\" msg=\"%s\"", logentry.Level, logentry.Msg)
 	}
 
 	if err := cmd.Wait(); err != nil {
diff --git a/libcontainer/system/proc_test.go b/libcontainer/system/proc_test.go
index 7e1acc5b..949d8403 100644
--- a/libcontainer/system/proc_test.go
+++ b/libcontainer/system/proc_test.go
@@ -1,45 +1,180 @@
 package system
 
-import "testing"
+import (
+	"errors"
+	"math/bits"
+	"os"
+	"reflect"
+	"strconv"
+	"testing"
+)
 
-func TestParseStartTime(t *testing.T) {
-	data := map[string]Stat_t{
-		"4902 (gunicorn: maste) S 4885 4902 4902 0 -1 4194560 29683 29929 61 83 78 16 96 17 20 0 1 0 9126532 52965376 1903 18446744073709551615 4194304 7461796 140733928751520 140733928698072 139816984959091 0 0 16781312 137447943 1 0 0 17 3 0 0 9 0 0 9559488 10071156 33050624 140733928758775 140733928758945 140733928758945 140733928759264 0": {
-			PID:       4902,
-			Name:      "gunicorn: maste",
-			State:     'S',
-			StartTime: 9126532,
+var procdata = map[string]Stat_t{
+	"4902 (gunicorn: maste) S 4885 4902 4902 0 -1 4194560 29683 29929 61 83 78 16 96 17 20 0 1 0 9126532 52965376 1903 18446744073709551615 4194304 7461796 140733928751520 140733928698072 139816984959091 0 0 16781312 137447943 1 0 0 17 3 0 0 9 0 0 9559488 10071156 33050624 140733928758775 140733928758945 140733928758945 140733928759264 0": {
+		Name:      "gunicorn: maste",
+		State:     'S',
+		StartTime: 9126532,
+	},
+	"9534 (cat) R 9323 9534 9323 34828 9534 4194304 95 0 0 0 0 0 0 0 20 0 1 0 9214966 7626752 168 18446744073709551615 4194304 4240332 140732237651568 140732237650920 140570710391216 0 0 0 0 0 0 0 17 1 0 0 0 0 0 6340112 6341364 21553152 140732237653865 140732237653885 140732237653885 140732237656047 0": {
+		Name:      "cat",
+		State:     'R',
+		StartTime: 9214966,
+	},
+	"12345 ((ugly )pr()cess() R 9323 9534 9323 34828 9534 4194304 95 0 0 0 0 0 0 0 20 0 1 0 9214966 7626752 168 18446744073709551615 4194304 4240332 140732237651568 140732237650920 140570710391216 0 0 0 0 0 0 0 17 1 0 0 0 0 0 6340112 6341364 21553152 140732237653865 140732237653885 140732237653885 140732237656047 0": {
+		Name:      "(ugly )pr()cess(",
+		State:     'R',
+		StartTime: 9214966,
+	},
+	"24767 (irq/44-mei_me) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 -51 0 1 0 8722075 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 1 50 1 0 0 0 0 0 0 0 0 0 0 0": {
+		Name:      "irq/44-mei_me",
+		State:     'S',
+		StartTime: 8722075,
+	},
+	"0 () I 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0": {
+		Name:      "",
+		State:     'I',
+		StartTime: 0,
+	},
+	// Not entirely correct, but minimally viable input (StartTime and a space after).
+	"1 (woo hoo) S 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 ": {
+		Name:      "woo hoo",
+		State:     'S',
+		StartTime: 4,
+	},
+}
+
+func TestParseStat(t *testing.T) {
+	for line, exp := range procdata {
+		st, err := parseStat(line)
+		if err != nil {
+			t.Errorf("input %q, unexpected error %v", line, err)
+		} else if !reflect.DeepEqual(st, exp) {
+			t.Errorf("input %q, expected %+v, got %+v", line, exp, st)
+		}
+	}
+}
+
+func TestParseStatBadInput(t *testing.T) {
+	cases := []struct {
+		desc, input string
+	}{
+		{
+			"no (",
+			"123 ) S 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
 		},
-		"9534 (cat) R 9323 9534 9323 34828 9534 4194304 95 0 0 0 0 0 0 0 20 0 1 0 9214966 7626752 168 18446744073709551615 4194304 4240332 140732237651568 140732237650920 140570710391216 0 0 0 0 0 0 0 17 1 0 0 0 0 0 6340112 6341364 21553152 140732237653865 140732237653885 140732237653885 140732237656047 0": {
-			PID:       9534,
-			Name:      "cat",
-			State:     'R',
-			StartTime: 9214966,
+		{
+			"no )",
+			"123 ( S 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
 		},
-
-		"24767 (irq/44-mei_me) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 -51 0 1 0 8722075 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 1 50 1 0 0 0 0 0 0 0 0 0 0 0": {
-			PID:       24767,
-			Name:      "irq/44-mei_me",
-			State:     'S',
-			StartTime: 8722075,
+		{
+			") at end",
+			"123 (cmd) S 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0)",
+		},
+		{
+			"misplaced ()",
+			"123 )one( S 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
+		},
+		{
+			"misplaced empty ()",
+			"123 )( S 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
+		},
+		{
+			"empty line",
+			"",
+		},
+		{
+			"short line",
+			"123 (cmd) S 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0",
+		},
+		{
+			"short line (no space after stime)",
+			"123 (cmd) S 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42",
+		},
+		{
+			"bad stime",
+			"123 (cmd) S 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 ",
+		},
+		{
+			"bad stime 2", // would be valid if not -1
+			"123 (cmd) S                   -1 ",
+		},
+		{
+			"a tad short",
+			"1234 (cmd) ",
+		},
+		{
+			"bad stime",
+			"123 (cmd) S 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1",
 		},
 	}
-	for line, expected := range data {
-		st, err := parseStat(line)
-		if err != nil {
-			t.Fatal(err)
+	for _, c := range cases {
+		st, err := parseStat(c.input)
+		if err == nil {
+			t.Errorf("case %q, expected error, got nil, %+v", c.desc, st)
 		}
-		if st.PID != expected.PID {
-			t.Fatalf("expected PID %q but received %q", expected.PID, st.PID)
+	}
+}
+
+func BenchmarkParseStat(b *testing.B) {
+	var (
+		st, exp Stat_t
+		line    string
+		err     error
+	)
+
+	for i := 0; i != b.N; i++ {
+		for line, exp = range procdata {
+			st, err = parseStat(line)
 		}
-		if st.State != expected.State {
-			t.Fatalf("expected state %q but received %q", expected.State, st.State)
+	}
+	if err != nil {
+		b.Fatal(err)
+	}
+	if !reflect.DeepEqual(st, exp) {
+		b.Fatal("wrong result")
+	}
+}
+
+func BenchmarkParseRealStat(b *testing.B) {
+	var (
+		st    Stat_t
+		err   error
+		total int
+	)
+	b.StopTimer()
+	fd, err := os.Open("/proc")
+	if err != nil {
+		b.Fatal(err)
+	}
+	defer fd.Close()
+
+	for i := 0; i != b.N; i++ {
+		count := 0
+		if _, err := fd.Seek(0, 0); err != nil {
+			b.Fatal(err)
 		}
-		if st.Name != expected.Name {
-			t.Fatalf("expected name %q but received %q", expected.Name, st.Name)
+		names, err := fd.Readdirnames(-1)
+		if err != nil {
+			b.Fatal(err)
 		}
-		if st.StartTime != expected.StartTime {
-			t.Fatalf("expected start time %q but received %q", expected.StartTime, st.StartTime)
+		for _, n := range names {
+			pid, err := strconv.ParseUint(n, 10, bits.UintSize)
+			if err != nil {
+				continue
+			}
+			b.StartTimer()
+			st, err = Stat(int(pid))
+			b.StopTimer()
+			if err != nil {
+				// Ignore a process that just finished.
+				if errors.Is(err, os.ErrNotExist) {
+					continue
+				}
+				b.Fatal(err)
+			}
+			count++
 		}
+		total += count
 	}
+	b.Logf("N: %d, parsed %d pids, last stat: %+v, err: %v", b.N, total, st, err)
 }
diff --git a/tests/integration/exec.bats b/tests/integration/exec.bats
index a49112fa..039041cc 100644
--- a/tests/integration/exec.bats
+++ b/tests/integration/exec.bats
@@ -21,26 +21,6 @@ function teardown() {
 	[[ "${output}" == *"Hello from exec"* ]]
 }
 
-@test "runc exec [exit codes]" {
-	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
-	[ "$status" -eq 0 ]
-
-	runc exec test_busybox false
-	[ "$status" -eq 1 ]
-
-	runc exec test_busybox sh -c "exit 42"
-	[ "$status" -eq 42 ]
-
-	runc exec --pid-file /non-existent/directory test_busybox true
-	[ "$status" -eq 255 ]
-
-	runc exec test_busybox no-such-binary
-	[ "$status" -eq 255 ]
-
-	runc exec no_such_container true
-	[ "$status" -eq 255 ]
-}
-
 @test "runc exec --pid-file" {
 	# run busybox detached
 	runc run -d --console-socket "$CONSOLE_SOCKET" test_busybox
diff --git a/tests/integration/hooks.bats b/tests/integration/hooks.bats
index 01c1280e..0c57bbc7 100644
--- a/tests/integration/hooks.bats
+++ b/tests/integration/hooks.bats
@@ -6,7 +6,7 @@ function setup() {
 	requires root no_systemd
 
 	setup_debian
-	# CR = CreateRuntime, CC = CreateContainer
+	# CR = CreateRuntime, CC = CreataContainer
 	HOOKLIBCR=librunc-hooks-create-runtime.so
 	HOOKLIBCC=librunc-hooks-create-container.so
 	LIBPATH="$(pwd)/rootfs/lib/"
diff --git a/tests/integration/start_hello.bats b/tests/integration/start_hello.bats
index 1c3c906f..85884703 100644
--- a/tests/integration/start_hello.bats
+++ b/tests/integration/start_hello.bats
@@ -76,15 +76,3 @@ function teardown() {
 	runc run test_hello
 	[ "$status" -eq 0 ]
 }
-
-@test "runc run [redundant seccomp rules]" {
-	update_config '	  .linux.seccomp = {
-				"defaultAction": "SCMP_ACT_ALLOW",
-				"syscalls": [{
-					"names": ["bdflush"],
-					"action": "SCMP_ACT_ALLOW",
-				}]
-			    }'
-	runc run test_hello
-	[ "$status" -eq 0 ]
-}