libct/cg/fscommon.OpenFile: speed up ro case

Commit 88e8350de2, among the other things, replaced filepath.Join with
securejoin.SecureJoin for both reads and writes to cgroupfs.

Commits e76ac1c054 and 31f0f5b7e0 switched more code to use
fscommon.ReadFile (and thus securejoin). Commit 0228226e6d introduced
fscommon.OpenFile (which uses securejoin as the fallback if openat2(2)
is not available, which is the case for older kernels), and commit
c95e69007c switched most of cgroup/fs[2] code to use it.

As a result, fs.GetStats() method became noticeable slower, mostly due
to securejoin calling os.Lstat and filepath.Clean.

Using securejoin as a security measure for cgroupfs files is
not well justified, as cgroupfs do not contain symlinks, and none of the
code using it have uncleaned paths. In particular, fs/fs2/systemd
managers do check and sanitize their paths.

This commit modifies the code to not use securejoin. Instead, it checks
that the opened file is indeed on cgroupfs.

Using BenchmarkGetStats on a CentOS 8 VM, I see the following
improvement:

Before:
> BenchmarkGetStats-8               8376            625135 ns/op

After:
> BenchmarkGetStats-8   	   12226	    485015 ns/op

An intermediate version, with no fstatfs to check fstype:
> BenchmarkGetStats-8              13162            452281 ns/op

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
Kir Kolyshkin
2021-04-29 14:09:59 -07:00
parent 9efd8466ab
commit 850b2c47b2
3 changed files with 36 additions and 7 deletions
+8
View File
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fscommon"
"github.com/opencontainers/runc/libcontainer/configs"
)
@@ -110,6 +111,13 @@ func BenchmarkGetStats(b *testing.B) {
b.Skip("cgroup v2 is not supported")
}
// Unset TestMode as we work with real cgroupfs here,
// and we want OpenFile to perform the fstype check.
fscommon.TestMode = false
defer func() {
fscommon.TestMode = true
}()
cg := &configs.Cgroup{
Path: "/some/kind/of/a/path/here",
Resources: &configs.Resources{},
+24 -7
View File
@@ -5,7 +5,6 @@ import (
"strings"
"sync"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
@@ -17,7 +16,7 @@ const (
)
var (
// Set to true by fs unit tests
// TestMode is set to true by unit tests that need "fake" cgroupfs.
TestMode bool
cgroupFd int = -1
@@ -72,11 +71,11 @@ func OpenFile(dir, file string, flags int) (*os.File, error) {
mode = 0o600
}
if prepareOpenat2() != nil {
return openWithSecureJoin(dir, file, flags, mode)
return openFallback(dir, file, flags, mode)
}
reldir := strings.TrimPrefix(dir, cgroupfsPrefix)
if len(reldir) == len(dir) { // non-standard path, old system?
return openWithSecureJoin(dir, file, flags, mode)
return openFallback(dir, file, flags, mode)
}
relname := reldir + "/" + file
@@ -93,11 +92,29 @@ func OpenFile(dir, file string, flags int) (*os.File, error) {
return os.NewFile(uintptr(fd), cgroupfsPrefix+relname), nil
}
func openWithSecureJoin(dir, file string, flags int, mode os.FileMode) (*os.File, error) {
path, err := securejoin.SecureJoin(dir, file)
var errNotCgroupfs = errors.New("not a cgroup file")
// openFallback is used when openat2(2) is not available. It checks the opened
// file is on cgroupfs, returning an error otherwise.
func openFallback(dir, file string, flags int, mode os.FileMode) (*os.File, error) {
path := dir + "/" + file
fd, err := os.OpenFile(path, flags, mode)
if err != nil {
return nil, err
}
if TestMode {
return fd, nil
}
// Check this is a cgroupfs file.
var st unix.Statfs_t
if err := unix.Fstatfs(int(fd.Fd()), &st); err != nil {
_ = fd.Close()
return nil, &os.PathError{Op: "statfs", Path: path, Err: err}
}
if st.Type != unix.CGROUP_SUPER_MAGIC && st.Type != unix.CGROUP2_SUPER_MAGIC {
_ = fd.Close()
return nil, &os.PathError{Op: "open", Path: path, Err: errNotCgroupfs}
}
return os.OpenFile(path, flags, mode)
return fd, nil
}
@@ -17,6 +17,10 @@ const (
floatString = "2048"
)
func init() {
TestMode = true
}
func TestGetCgroupParamsInt(t *testing.T) {
// Setup tempdir.
tempDir, err := ioutil.TempDir("", "cgroup_utils_test")