Merge pull request #2412 from lifubang/removecgpath

remove cgroup path recursively in cgroup v2
diff --git a/libcontainer/cgroups/fs2/fs2.go b/libcontainer/cgroups/fs2/fs2.go
index b7fe442..5566847 100644
--- a/libcontainer/cgroups/fs2/fs2.go
+++ b/libcontainer/cgroups/fs2/fs2.go
@@ -156,11 +156,35 @@
 	return nil
 }
 
-func (m *manager) Destroy() error {
-	if err := os.Remove(m.dirPath); err != nil && !os.IsNotExist(err) {
+// removeCgroupPath aims to remove cgroup path recursively
+// Because there may be subcgroups in it.
+func removeCgroupPath(path string) error {
+	infos, err := ioutil.ReadDir(path)
+	if err != nil {
+		if os.IsNotExist(err) {
+			err = nil
+		}
 		return err
 	}
-	return nil
+	for _, info := range infos {
+		if info.IsDir() {
+			// We should remove subcgroups dir first
+			if err = removeCgroupPath(filepath.Join(path, info.Name())); err != nil {
+				break
+			}
+		}
+	}
+	if err == nil {
+		err = os.Remove(path)
+		if os.IsNotExist(err) {
+			err = nil
+		}
+	}
+	return err
+}
+
+func (m *manager) Destroy() error {
+	return removeCgroupPath(m.dirPath)
 }
 
 func (m *manager) Path(_ string) string {
diff --git a/tests/integration/delete.bats b/tests/integration/delete.bats
index c5ed215..44a76fc 100644
--- a/tests/integration/delete.bats
+++ b/tests/integration/delete.bats
@@ -51,3 +51,58 @@
   runc delete --force notexists
   [ "$status" -eq 0 ]
 }
+
+@test "runc delete --force in cgroupv2 with subcgroups" {
+  requires cgroups_v2 root
+  set_cgroups_path "$BUSYBOX_BUNDLE"
+
+  # grant `rw` priviledge to `/sys/fs/cgroup`
+  cat "${BUSYBOX_BUNDLE}/config.json"\
+   | jq '.mounts |= map((select(.type=="cgroup") | .options -= ["ro"]) // .)'\
+   > "${BUSYBOX_BUNDLE}/config.json.tmp"
+  mv "${BUSYBOX_BUNDLE}/config.json"{.tmp,}
+
+  # run busybox detached
+  runc run -d --console-socket $CONSOLE_SOCKET test_busybox
+  [ "$status" -eq 0 ]
+
+  # check state
+  testcontainer test_busybox running
+
+  # create a sub process
+  __runc exec -d test_busybox sleep 1d
+  [ "$status" -eq 0 ]
+
+  # find the pid of sleep
+  pid=$(__runc exec test_busybox ps -a | grep 1d | awk '{print $1}')
+  [[ ${pid} =~ [0-9]+ ]]
+
+  # create subcgroups
+  cat <<EOF > nest.sh
+  cd /sys/fs/cgroup
+  for f in \$(cat cgroup.controllers); do echo +\$f > cgroup.subtree_control; done
+  mkdir foo
+  cd foo
+  echo threaded > cgroup.type
+  echo ${pid} > cgroup.threads
+  cat cgroup.threads
+EOF
+  cat nest.sh | runc exec test_busybox sh
+  [[ ${output} =~ [0-9]+ ]]
+
+  # check create subcgroups success
+  [ -d $CGROUP_PATH/foo ]
+
+  # check cgroup.threads' value
+  runc exec test_busybox cat /sys/fs/cgroup/foo/cgroup.threads
+  [[ ${output} =~ [0-9]+ ]]
+
+  # force delete test_busybox
+  runc delete --force test_busybox
+
+  runc state test_busybox
+  [ "$status" -ne 0 ]
+
+  # check delete subcgroups success
+  [ ! -d $CGROUP_PATH/foo ]
+}