Skip to content

Commit 1b42ebc

Browse files
authored
Merge pull request #4531 from lifubang/backport-4523
[1.2] runc delete: fix for rootless cgroup + ro cgroupfs
2 parents 8bfc3b5 + 832faf0 commit 1b42ebc

File tree

2 files changed

+48
-8
lines changed

2 files changed

+48
-8
lines changed

libcontainer/cgroups/utils.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,27 +251,39 @@ again:
251251
// RemovePath aims to remove cgroup path. It does so recursively,
252252
// by removing any subdirectories (sub-cgroups) first.
253253
func RemovePath(path string) error {
254-
// Try the fast path first.
254+
// Try the fast path first; don't retry on EBUSY yet.
255255
if err := rmdir(path, false); err == nil {
256256
return nil
257257
}
258258

259+
// There are many reasons why rmdir can fail, including:
260+
// 1. cgroup have existing sub-cgroups;
261+
// 2. cgroup (still) have some processes (that are about to vanish);
262+
// 3. lack of permission (one example is read-only /sys/fs/cgroup mount,
263+
// in which case rmdir returns EROFS even for for a non-existent path,
264+
// see issue 4518).
265+
//
266+
// Using os.ReadDir here kills two birds with one stone: check if
267+
// the directory exists (handling scenario 3 above), and use
268+
// directory contents to remove sub-cgroups (handling scenario 1).
259269
infos, err := os.ReadDir(path)
260-
if err != nil && !os.IsNotExist(err) {
270+
if err != nil {
271+
if os.IsNotExist(err) {
272+
return nil
273+
}
261274
return err
262275
}
276+
// Let's remove sub-cgroups, if any.
263277
for _, info := range infos {
264278
if info.IsDir() {
265-
// We should remove subcgroup first.
266279
if err = RemovePath(filepath.Join(path, info.Name())); err != nil {
267-
break
280+
return err
268281
}
269282
}
270283
}
271-
if err == nil {
272-
err = rmdir(path, true)
273-
}
274-
return err
284+
// Finally, try rmdir again, this time with retries on EBUSY,
285+
// which may help with scenario 2 above.
286+
return rmdir(path, true)
275287
}
276288

277289
// RemovePaths iterates over the provided paths removing them.

libcontainer/cgroups/utils_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package cgroups
33
import (
44
"bytes"
55
"errors"
6+
"path/filepath"
67
"reflect"
78
"strings"
89
"testing"
910

1011
"github.com/moby/sys/mountinfo"
12+
"golang.org/x/sys/unix"
1113
)
1214

1315
const fedoraMountinfo = `15 35 0:3 / /proc rw,nosuid,nodev,noexec,relatime shared:5 - proc proc rw
@@ -661,3 +663,29 @@ func TestConvertBlkIOToIOWeightValue(t *testing.T) {
661663
}
662664
}
663665
}
666+
667+
// TestRemovePathReadOnly is to test remove a non-existent dir in a ro mount point.
668+
// The similar issue example: https://github.com/opencontainers/runc/issues/4518
669+
func TestRemovePathReadOnly(t *testing.T) {
670+
dirTo := t.TempDir()
671+
err := unix.Mount(t.TempDir(), dirTo, "", unix.MS_BIND, "")
672+
if err != nil {
673+
t.Skip("no permission of mount")
674+
}
675+
defer func() {
676+
_ = unix.Unmount(dirTo, 0)
677+
}()
678+
err = unix.Mount("", dirTo, "", unix.MS_REMOUNT|unix.MS_BIND|unix.MS_RDONLY, "")
679+
if err != nil {
680+
t.Skip("no permission of mount")
681+
}
682+
nonExistentDir := filepath.Join(dirTo, "non-existent-dir")
683+
err = rmdir(nonExistentDir, true)
684+
if !errors.Is(err, unix.EROFS) {
685+
t.Fatalf("expected the error of removing a non-existent dir %s in a ro mount point with rmdir to be unix.EROFS, but got: %v", nonExistentDir, err)
686+
}
687+
err = RemovePath(nonExistentDir)
688+
if err != nil {
689+
t.Fatalf("expected the error of removing a non-existent dir %s in a ro mount point with RemovePath to be nil, but got: %v", nonExistentDir, err)
690+
}
691+
}

0 commit comments

Comments
 (0)