Implement sysfs.

PiperOrigin-RevId: 290822487
This commit is contained in:
Rahat Mahmood
2020-01-21 15:13:26 -08:00
committed by gVisor bot
parent dc99897205
commit ad1968ed56
11 changed files with 519 additions and 171 deletions
+13
View File
@@ -180,6 +180,19 @@ const (
DT_WHT = 14
)
// DirentType are the friendly strings for linux_dirent64.d_type.
var DirentType = abi.ValueSet{
DT_UNKNOWN: "DT_UNKNOWN",
DT_FIFO: "DT_FIFO",
DT_CHR: "DT_CHR",
DT_DIR: "DT_DIR",
DT_BLK: "DT_BLK",
DT_REG: "DT_REG",
DT_LNK: "DT_LNK",
DT_SOCK: "DT_SOCK",
DT_WHT: "DT_WHT",
}
// Values for preadv2/pwritev2.
const (
// Note: gVisor does not implement the RWF_HIPRI feature, but the flag is
+1 -2
View File
@@ -51,13 +51,12 @@ go_test(
deps = [
":kernfs",
"//pkg/abi/linux",
"//pkg/fspath",
"//pkg/sentry/context",
"//pkg/sentry/context/contexttest",
"//pkg/sentry/fsimpl/testutil",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/usermem",
"//pkg/sentry/vfs",
"//pkg/sync",
"//pkg/syserror",
"@com_github_google_go-cmp//cmp:go_default_library",
],
+29 -146
View File
@@ -17,20 +17,17 @@ package kernfs_test
import (
"bytes"
"fmt"
"io"
"runtime"
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
"gvisor.dev/gvisor/pkg/syserror"
)
@@ -41,21 +38,11 @@ const staticFileContent = "This is sample content for a static test file."
// filesystem. See newTestSystem.
type RootDentryFn func(*auth.Credentials, *filesystem) *kernfs.Dentry
// TestSystem represents the context for a single test.
type TestSystem struct {
t *testing.T
ctx context.Context
creds *auth.Credentials
vfs *vfs.VirtualFilesystem
mns *vfs.MountNamespace
root vfs.VirtualDentry
}
// newTestSystem sets up a minimal environment for running a test, including an
// instance of a test filesystem. Tests can control the contents of the
// filesystem by providing an appropriate rootFn, which should return a
// pre-populated root dentry.
func newTestSystem(t *testing.T, rootFn RootDentryFn) *TestSystem {
func newTestSystem(t *testing.T, rootFn RootDentryFn) *testutil.System {
ctx := contexttest.Context(t)
creds := auth.CredentialsFromContext(ctx)
v := vfs.New()
@@ -66,57 +53,7 @@ func newTestSystem(t *testing.T, rootFn RootDentryFn) *TestSystem {
if err != nil {
t.Fatalf("Failed to create testfs root mount: %v", err)
}
s := &TestSystem{
t: t,
ctx: ctx,
creds: creds,
vfs: v,
mns: mns,
root: mns.Root(),
}
runtime.SetFinalizer(s, func(s *TestSystem) { s.root.DecRef() })
return s
}
// PathOpAtRoot constructs a vfs.PathOperation for a path from the
// root of the test filesystem.
//
// Precondition: path should be relative path.
func (s *TestSystem) PathOpAtRoot(path string) vfs.PathOperation {
return vfs.PathOperation{
Root: s.root,
Start: s.root,
Path: fspath.Parse(path),
}
}
// GetDentryOrDie attempts to resolve a dentry referred to by the
// provided path operation. If unsuccessful, the test fails.
func (s *TestSystem) GetDentryOrDie(pop vfs.PathOperation) vfs.VirtualDentry {
vd, err := s.vfs.GetDentryAt(s.ctx, s.creds, &pop, &vfs.GetDentryOptions{})
if err != nil {
s.t.Fatalf("GetDentryAt(pop:%+v) failed: %v", pop, err)
}
return vd
}
func (s *TestSystem) ReadToEnd(fd *vfs.FileDescription) (string, error) {
buf := make([]byte, usermem.PageSize)
bufIOSeq := usermem.BytesIOSequence(buf)
opts := vfs.ReadOptions{}
var content bytes.Buffer
for {
n, err := fd.Impl().Read(s.ctx, bufIOSeq, opts)
if n == 0 || err != nil {
if err == io.EOF {
err = nil
}
return content.String(), err
}
content.Write(buf[:n])
}
return testutil.NewSystem(ctx, t, v, mns)
}
type fsType struct {
@@ -260,6 +197,7 @@ func TestBasic(t *testing.T) {
"file1": fs.newFile(creds, staticFileContent),
})
})
defer sys.Destroy()
sys.GetDentryOrDie(sys.PathOpAtRoot("file1")).DecRef()
}
@@ -269,9 +207,10 @@ func TestMkdirGetDentry(t *testing.T) {
"dir1": fs.newDir(creds, 0755, nil),
})
})
defer sys.Destroy()
pop := sys.PathOpAtRoot("dir1/a new directory")
if err := sys.vfs.MkdirAt(sys.ctx, sys.creds, &pop, &vfs.MkdirOptions{Mode: 0755}); err != nil {
if err := sys.VFS.MkdirAt(sys.Ctx, sys.Creds, &pop, &vfs.MkdirOptions{Mode: 0755}); err != nil {
t.Fatalf("MkdirAt for PathOperation %+v failed: %v", pop, err)
}
sys.GetDentryOrDie(pop).DecRef()
@@ -283,20 +222,21 @@ func TestReadStaticFile(t *testing.T) {
"file1": fs.newFile(creds, staticFileContent),
})
})
defer sys.Destroy()
pop := sys.PathOpAtRoot("file1")
fd, err := sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, &vfs.OpenOptions{})
fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, &pop, &vfs.OpenOptions{})
if err != nil {
sys.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
}
defer fd.DecRef()
content, err := sys.ReadToEnd(fd)
if err != nil {
sys.t.Fatalf("Read failed: %v", err)
t.Fatalf("Read failed: %v", err)
}
if diff := cmp.Diff(staticFileContent, content); diff != "" {
sys.t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff)
t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff)
}
}
@@ -306,83 +246,44 @@ func TestCreateNewFileInStaticDir(t *testing.T) {
"dir1": fs.newDir(creds, 0755, nil),
})
})
defer sys.Destroy()
pop := sys.PathOpAtRoot("dir1/newfile")
opts := &vfs.OpenOptions{Flags: linux.O_CREAT | linux.O_EXCL, Mode: defaultMode}
fd, err := sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, opts)
fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, &pop, opts)
if err != nil {
sys.t.Fatalf("OpenAt(pop:%+v, opts:%+v) failed: %v", pop, opts, err)
t.Fatalf("OpenAt(pop:%+v, opts:%+v) failed: %v", pop, opts, err)
}
// Close the file. The file should persist.
fd.DecRef()
fd, err = sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, &vfs.OpenOptions{})
fd, err = sys.VFS.OpenAt(sys.Ctx, sys.Creds, &pop, &vfs.OpenOptions{})
if err != nil {
sys.t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err)
t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err)
}
fd.DecRef()
}
// direntCollector provides an implementation for vfs.IterDirentsCallback for
// testing. It simply iterates to the end of a given directory FD and collects
// all dirents emitted by the callback.
type direntCollector struct {
mu sync.Mutex
dirents map[string]vfs.Dirent
}
// Handle implements vfs.IterDirentsCallback.Handle.
func (d *direntCollector) Handle(dirent vfs.Dirent) bool {
d.mu.Lock()
if d.dirents == nil {
d.dirents = make(map[string]vfs.Dirent)
}
d.dirents[dirent.Name] = dirent
d.mu.Unlock()
return true
}
// count returns the number of dirents currently in the collector.
func (d *direntCollector) count() int {
d.mu.Lock()
defer d.mu.Unlock()
return len(d.dirents)
}
// contains checks whether the collector has a dirent with the given name and
// type.
func (d *direntCollector) contains(name string, typ uint8) error {
d.mu.Lock()
defer d.mu.Unlock()
dirent, ok := d.dirents[name]
if !ok {
return fmt.Errorf("No dirent named %q found", name)
}
if dirent.Type != typ {
return fmt.Errorf("Dirent named %q found, but was expecting type %d, got: %+v", name, typ, dirent)
}
return nil
}
func TestDirFDReadWrite(t *testing.T) {
sys := newTestSystem(t, func(creds *auth.Credentials, fs *filesystem) *kernfs.Dentry {
return fs.newReadonlyDir(creds, 0755, nil)
})
defer sys.Destroy()
pop := sys.PathOpAtRoot("/")
fd, err := sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, &vfs.OpenOptions{})
fd, err := sys.VFS.OpenAt(sys.Ctx, sys.Creds, &pop, &vfs.OpenOptions{})
if err != nil {
sys.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
}
defer fd.DecRef()
// Read/Write should fail for directory FDs.
if _, err := fd.Read(sys.ctx, usermem.BytesIOSequence([]byte{}), vfs.ReadOptions{}); err != syserror.EISDIR {
sys.t.Fatalf("Read for directory FD failed with unexpected error: %v", err)
if _, err := fd.Read(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.ReadOptions{}); err != syserror.EISDIR {
t.Fatalf("Read for directory FD failed with unexpected error: %v", err)
}
if _, err := fd.Write(sys.ctx, usermem.BytesIOSequence([]byte{}), vfs.WriteOptions{}); err != syserror.EISDIR {
sys.t.Fatalf("Wrire for directory FD failed with unexpected error: %v", err)
if _, err := fd.Write(sys.Ctx, usermem.BytesIOSequence([]byte{}), vfs.WriteOptions{}); err != syserror.EISDIR {
t.Fatalf("Write for directory FD failed with unexpected error: %v", err)
}
}
@@ -397,30 +298,12 @@ func TestDirFDIterDirents(t *testing.T) {
"file1": fs.newFile(creds, staticFileContent),
})
})
defer sys.Destroy()
pop := sys.PathOpAtRoot("/")
fd, err := sys.vfs.OpenAt(sys.ctx, sys.creds, &pop, &vfs.OpenOptions{})
if err != nil {
sys.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
}
defer fd.DecRef()
collector := &direntCollector{}
if err := fd.IterDirents(sys.ctx, collector); err != nil {
sys.t.Fatalf("IterDirent failed: %v", err)
}
// Root directory should contain ".", ".." and 3 children:
if collector.count() != 5 {
sys.t.Fatalf("IterDirent returned too many dirents")
}
for _, dirName := range []string{".", "..", "dir1", "dir2"} {
if err := collector.contains(dirName, linux.DT_DIR); err != nil {
sys.t.Fatalf("IterDirent had unexpected results: %v", err)
}
}
if err := collector.contains("file1", linux.DT_REG); err != nil {
sys.t.Fatalf("IterDirent had unexpected results: %v", err)
}
sys.AssertDirectoryContains(&pop, map[string]testutil.DirentType{
"dir1": linux.DT_DIR,
"dir2": linux.DT_DIR,
"file1": linux.DT_REG,
})
}
+1 -12
View File
@@ -44,30 +44,19 @@ go_test(
name = "proc_test",
size = "small",
srcs = [
"boot_test.go",
"tasks_sys_test.go",
"tasks_test.go",
],
embed = [":proc"],
deps = [
"//pkg/abi/linux",
"//pkg/cpuid",
"//pkg/fspath",
"//pkg/memutil",
"//pkg/sentry/context",
"//pkg/sentry/context/contexttest",
"//pkg/sentry/fs",
"//pkg/sentry/fsimpl/testutil",
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/sched",
"//pkg/sentry/limits",
"//pkg/sentry/loader",
"//pkg/sentry/pgalloc",
"//pkg/sentry/platform",
"//pkg/sentry/platform/kvm",
"//pkg/sentry/platform/ptrace",
"//pkg/sentry/time",
"//pkg/sentry/usermem",
"//pkg/sentry/vfs",
"//pkg/syserror",
+7 -6
View File
@@ -24,6 +24,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/usermem"
@@ -134,7 +135,7 @@ func checkFiles(gots []vfs.Dirent, wants map[string]vfs.Dirent) ([]vfs.Dirent, e
}
func setup() (context.Context, *vfs.VirtualFilesystem, vfs.VirtualDentry, error) {
k, err := boot()
k, err := testutil.Boot()
if err != nil {
return nil, nil, vfs.VirtualDentry{}, fmt.Errorf("creating kernel: %v", err)
}
@@ -206,7 +207,7 @@ func TestTasks(t *testing.T) {
var tasks []*kernel.Task
for i := 0; i < 5; i++ {
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
task, err := createTask(ctx, fmt.Sprintf("name-%d", i), tc)
task, err := testutil.CreateTask(ctx, fmt.Sprintf("name-%d", i), tc)
if err != nil {
t.Fatalf("CreateTask(): %v", err)
}
@@ -298,7 +299,7 @@ func TestTasksOffset(t *testing.T) {
k := kernel.KernelFromContext(ctx)
for i := 0; i < 3; i++ {
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
if _, err := createTask(ctx, fmt.Sprintf("name-%d", i), tc); err != nil {
if _, err := testutil.CreateTask(ctx, fmt.Sprintf("name-%d", i), tc); err != nil {
t.Fatalf("CreateTask(): %v", err)
}
}
@@ -417,7 +418,7 @@ func TestTask(t *testing.T) {
k := kernel.KernelFromContext(ctx)
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
_, err = createTask(ctx, "name", tc)
_, err = testutil.CreateTask(ctx, "name", tc)
if err != nil {
t.Fatalf("CreateTask(): %v", err)
}
@@ -458,7 +459,7 @@ func TestProcSelf(t *testing.T) {
k := kernel.KernelFromContext(ctx)
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
task, err := createTask(ctx, "name", tc)
task, err := testutil.CreateTask(ctx, "name", tc)
if err != nil {
t.Fatalf("CreateTask(): %v", err)
}
@@ -555,7 +556,7 @@ func TestTree(t *testing.T) {
var tasks []*kernel.Task
for i := 0; i < 5; i++ {
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
task, err := createTask(uberCtx, fmt.Sprintf("name-%d", i), tc)
task, err := testutil.CreateTask(uberCtx, fmt.Sprintf("name-%d", i), tc)
if err != nil {
t.Fatalf("CreateTask(): %v", err)
}
+35
View File
@@ -0,0 +1,35 @@
load("//tools/go_stateify:defs.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_test")
package(licenses = ["notice"])
go_library(
name = "sys",
srcs = [
"sys.go",
],
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/sys",
deps = [
"//pkg/abi/linux",
"//pkg/sentry/context",
"//pkg/sentry/fsimpl/kernfs",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/vfs",
"//pkg/syserror",
],
)
go_test(
name = "sys_test",
srcs = ["sys_test.go"],
deps = [
":sys",
"//pkg/abi/linux",
"//pkg/sentry/fsimpl/testutil",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/vfs",
"@com_github_google_go-cmp//cmp:go_default_library",
],
)
+124
View File
@@ -0,0 +1,124 @@
// Copyright 2019 The gVisor 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 sys implements sysfs.
package sys
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
// FilesystemType implements vfs.FilesystemType.
type FilesystemType struct{}
// filesystem implements vfs.FilesystemImpl.
type filesystem struct {
kernfs.Filesystem
}
// GetFilesystem implements vfs.FilesystemType.GetFilesystem.
func (FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
fs := &filesystem{}
fs.Filesystem.Init(vfsObj)
k := kernel.KernelFromContext(ctx)
maxCPUCores := k.ApplicationCores()
defaultSysDirMode := linux.FileMode(0755)
root := fs.newDir(creds, defaultSysDirMode, map[string]*kernfs.Dentry{
"block": fs.newDir(creds, defaultSysDirMode, nil),
"bus": fs.newDir(creds, defaultSysDirMode, nil),
"class": fs.newDir(creds, defaultSysDirMode, map[string]*kernfs.Dentry{
"power_supply": fs.newDir(creds, defaultSysDirMode, nil),
}),
"dev": fs.newDir(creds, defaultSysDirMode, nil),
"devices": fs.newDir(creds, defaultSysDirMode, map[string]*kernfs.Dentry{
"system": fs.newDir(creds, defaultSysDirMode, map[string]*kernfs.Dentry{
"cpu": fs.newDir(creds, defaultSysDirMode, map[string]*kernfs.Dentry{
"online": fs.newCPUFile(creds, maxCPUCores, linux.FileMode(0444)),
"possible": fs.newCPUFile(creds, maxCPUCores, linux.FileMode(0444)),
"present": fs.newCPUFile(creds, maxCPUCores, linux.FileMode(0444)),
}),
}),
}),
"firmware": fs.newDir(creds, defaultSysDirMode, nil),
"fs": fs.newDir(creds, defaultSysDirMode, nil),
"kernel": fs.newDir(creds, defaultSysDirMode, nil),
"module": fs.newDir(creds, defaultSysDirMode, nil),
"power": fs.newDir(creds, defaultSysDirMode, nil),
})
return fs.VFSFilesystem(), root.VFSDentry(), nil
}
// dir implements kernfs.Inode.
type dir struct {
kernfs.InodeAttrs
kernfs.InodeNoDynamicLookup
kernfs.InodeNotSymlink
kernfs.InodeDirectoryNoNewChildren
kernfs.OrderedChildren
dentry kernfs.Dentry
}
func (fs *filesystem) newDir(creds *auth.Credentials, mode linux.FileMode, contents map[string]*kernfs.Dentry) *kernfs.Dentry {
d := &dir{}
d.InodeAttrs.Init(creds, fs.NextIno(), linux.ModeDirectory|0755)
d.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
d.dentry.Init(d)
d.IncLinks(d.OrderedChildren.Populate(&d.dentry, contents))
return &d.dentry
}
// SetStat implements kernfs.Inode.SetStat.
func (d *dir) SetStat(fs *vfs.Filesystem, opts vfs.SetStatOptions) error {
return syserror.EPERM
}
// Open implements kernfs.Inode.Open.
func (d *dir) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
fd := &kernfs.GenericDirectoryFD{}
fd.Init(rp.Mount(), vfsd, &d.OrderedChildren, flags)
return fd.VFSFileDescription(), nil
}
// cpuFile implements kernfs.Inode.
type cpuFile struct {
kernfs.DynamicBytesFile
maxCores uint
}
// Generate implements vfs.DynamicBytesSource.Generate.
func (c *cpuFile) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "0-%d", c.maxCores-1)
return nil
}
func (fs *filesystem) newCPUFile(creds *auth.Credentials, maxCores uint, mode linux.FileMode) *kernfs.Dentry {
c := &cpuFile{maxCores: maxCores}
c.DynamicBytesFile.Init(creds, fs.NextIno(), c, mode)
d := &kernfs.Dentry{}
d.Init(c)
return d
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2019 The gVisor 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 sys_test
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/sys"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
func newTestSystem(t *testing.T) *testutil.System {
k, err := testutil.Boot()
if err != nil {
t.Fatalf("Failed to create test kernel: %v", err)
}
ctx := k.SupervisorContext()
creds := auth.CredentialsFromContext(ctx)
v := vfs.New()
v.MustRegisterFilesystemType("sysfs", sys.FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
mns, err := v.NewMountNamespace(ctx, creds, "", "sysfs", &vfs.GetFilesystemOptions{})
if err != nil {
t.Fatalf("Failed to create new mount namespace: %v", err)
}
return testutil.NewSystem(ctx, t, v, mns)
}
func TestReadCPUFile(t *testing.T) {
s := newTestSystem(t)
defer s.Destroy()
k := kernel.KernelFromContext(s.Ctx)
maxCPUCores := k.ApplicationCores()
expected := fmt.Sprintf("0-%d", maxCPUCores-1)
for _, fname := range []string{"online", "possible", "present"} {
pop := s.PathOpAtRoot(fmt.Sprintf("devices/system/cpu/%s", fname))
fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, &pop, &vfs.OpenOptions{})
if err != nil {
t.Fatalf("OpenAt(pop:%+v) = %+v failed: %v", pop, fd, err)
}
defer fd.DecRef()
content, err := s.ReadToEnd(fd)
if err != nil {
t.Fatalf("Read failed: %v", err)
}
if diff := cmp.Diff(expected, content); diff != "" {
t.Fatalf("Read returned unexpected data:\n--- want\n+++ got\n%v", diff)
}
}
}
func TestSysRootContainsExpectedEntries(t *testing.T) {
s := newTestSystem(t)
defer s.Destroy()
pop := s.PathOpAtRoot("/")
s.AssertDirectoryContains(&pop, map[string]testutil.DirentType{
"block": linux.DT_DIR,
"bus": linux.DT_DIR,
"class": linux.DT_DIR,
"dev": linux.DT_DIR,
"devices": linux.DT_DIR,
"firmware": linux.DT_DIR,
"fs": linux.DT_DIR,
"kernel": linux.DT_DIR,
"module": linux.DT_DIR,
"power": linux.DT_DIR,
})
}
+36
View File
@@ -0,0 +1,36 @@
load("//tools/go_stateify:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "testutil",
testonly = 1,
srcs = [
"kernel.go",
"testutil.go",
],
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil",
visibility = ["//pkg/sentry:internal"],
deps = [
"//pkg/abi/linux",
"//pkg/cpuid",
"//pkg/fspath",
"//pkg/memutil",
"//pkg/sentry/context",
"//pkg/sentry/fs",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/sched",
"//pkg/sentry/limits",
"//pkg/sentry/loader",
"//pkg/sentry/pgalloc",
"//pkg/sentry/platform",
"//pkg/sentry/platform/kvm",
"//pkg/sentry/platform/ptrace",
"//pkg/sentry/time",
"//pkg/sentry/usermem",
"//pkg/sentry/vfs",
"//pkg/sync",
"@com_github_google_go-cmp//cmp:go_default_library",
],
)
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package proc
package testutil
import (
"flag"
@@ -43,8 +43,8 @@ var (
platformFlag = flag.String("platform", "ptrace", "specify which platform to use")
)
// boot initializes a new bare bones kernel for test.
func boot() (*kernel.Kernel, error) {
// Boot initializes a new bare bones kernel for test.
func Boot() (*kernel.Kernel, error) {
platformCtr, err := platform.Lookup(*platformFlag)
if err != nil {
return nil, fmt.Errorf("platform not found: %v", err)
@@ -117,8 +117,8 @@ func boot() (*kernel.Kernel, error) {
return k, nil
}
// createTask creates a new bare bones task for tests.
func createTask(ctx context.Context, name string, tc *kernel.ThreadGroup) (*kernel.Task, error) {
// CreateTask creates a new bare bones task for tests.
func CreateTask(ctx context.Context, name string, tc *kernel.ThreadGroup) (*kernel.Task, error) {
k := kernel.KernelFromContext(ctx)
config := &kernel.TaskConfig{
Kernel: k,
+178
View File
@@ -0,0 +1,178 @@
// Copyright 2019 The gVisor 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 testutil provides common test utilities for kernfs-based
// filesystems.
package testutil
import (
"fmt"
"io"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/sync"
)
// System represents the context for a single test.
//
// Test systems must be explicitly destroyed with System.Destroy.
type System struct {
t *testing.T
Ctx context.Context
Creds *auth.Credentials
VFS *vfs.VirtualFilesystem
mns *vfs.MountNamespace
root vfs.VirtualDentry
}
// NewSystem constructs a System.
//
// Precondition: Caller must hold a reference on mns, whose ownership
// is transferred to the new System.
func NewSystem(ctx context.Context, t *testing.T, v *vfs.VirtualFilesystem, mns *vfs.MountNamespace) *System {
s := &System{
t: t,
Ctx: ctx,
Creds: auth.CredentialsFromContext(ctx),
VFS: v,
mns: mns,
root: mns.Root(),
}
return s
}
// Destroy release resources associated with a test system.
func (s *System) Destroy() {
s.root.DecRef()
s.mns.DecRef(s.VFS) // Reference on mns passed to NewSystem.
}
// ReadToEnd reads the contents of fd until EOF to a string.
func (s *System) ReadToEnd(fd *vfs.FileDescription) (string, error) {
buf := make([]byte, usermem.PageSize)
bufIOSeq := usermem.BytesIOSequence(buf)
opts := vfs.ReadOptions{}
var content strings.Builder
for {
n, err := fd.Read(s.Ctx, bufIOSeq, opts)
if n == 0 || err != nil {
if err == io.EOF {
err = nil
}
return content.String(), err
}
content.Write(buf[:n])
}
}
// PathOpAtRoot constructs a PathOperation with the given path from
// the root of the filesystem.
func (s *System) PathOpAtRoot(path string) vfs.PathOperation {
return vfs.PathOperation{
Root: s.root,
Start: s.root,
Path: fspath.Parse(path),
}
}
// GetDentryOrDie attempts to resolve a dentry referred to by the
// provided path operation. If unsuccessful, the test fails.
func (s *System) GetDentryOrDie(pop vfs.PathOperation) vfs.VirtualDentry {
vd, err := s.VFS.GetDentryAt(s.Ctx, s.Creds, &pop, &vfs.GetDentryOptions{})
if err != nil {
s.t.Fatalf("GetDentryAt(pop:%+v) failed: %v", pop, err)
}
return vd
}
// DirentType is an alias for values for linux_dirent64.d_type.
type DirentType = uint8
// AssertDirectoryContains verifies that a directory at pop contains the entries
// specified. AssertDirectoryContains implicitly checks for "." and "..", these
// need not be included in entries.
func (s *System) AssertDirectoryContains(pop *vfs.PathOperation, entries map[string]DirentType) {
// Also implicitly check for "." and "..".
entries["."] = linux.DT_DIR
entries[".."] = linux.DT_DIR
fd, err := s.VFS.OpenAt(s.Ctx, s.Creds, pop, &vfs.OpenOptions{Flags: linux.O_RDONLY})
if err != nil {
s.t.Fatalf("OpenAt for PathOperation %+v failed: %v", pop, err)
}
defer fd.DecRef()
collector := &DirentCollector{}
if err := fd.IterDirents(s.Ctx, collector); err != nil {
s.t.Fatalf("IterDirent failed: %v", err)
}
collectedEntries := make(map[string]DirentType)
for _, dirent := range collector.dirents {
collectedEntries[dirent.Name] = dirent.Type
}
if diff := cmp.Diff(entries, collectedEntries); diff != "" {
s.t.Fatalf("IterDirent had unexpected results:\n--- want\n+++ got\n%v", diff)
}
}
// DirentCollector provides an implementation for vfs.IterDirentsCallback for
// testing. It simply iterates to the end of a given directory FD and collects
// all dirents emitted by the callback.
type DirentCollector struct {
mu sync.Mutex
dirents map[string]vfs.Dirent
}
// Handle implements vfs.IterDirentsCallback.Handle.
func (d *DirentCollector) Handle(dirent vfs.Dirent) bool {
d.mu.Lock()
if d.dirents == nil {
d.dirents = make(map[string]vfs.Dirent)
}
d.dirents[dirent.Name] = dirent
d.mu.Unlock()
return true
}
// Count returns the number of dirents currently in the collector.
func (d *DirentCollector) Count() int {
d.mu.Lock()
defer d.mu.Unlock()
return len(d.dirents)
}
// Contains checks whether the collector has a dirent with the given name and
// type.
func (d *DirentCollector) Contains(name string, typ uint8) error {
d.mu.Lock()
defer d.mu.Unlock()
dirent, ok := d.dirents[name]
if !ok {
return fmt.Errorf("No dirent named %q found", name)
}
if dirent.Type != typ {
return fmt.Errorf("Dirent named %q found, but was expecting type %s, got: %+v", name, linux.DirentType.Parse(uint64(typ)), dirent)
}
return nil
}