mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement sysfs.
PiperOrigin-RevId: 290822487
This commit is contained in:
committed by
gVisor bot
parent
dc99897205
commit
ad1968ed56
@@ -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
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user