mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Initial procfs implementation in VFSv2
Updates #1195 PiperOrigin-RevId: 287227722
This commit is contained in:
committed by
gVisor bot
parent
5b9034cc18
commit
3c125eb219
@@ -25,6 +25,7 @@ go_library(
|
||||
"inode_impl_util.go",
|
||||
"kernfs.go",
|
||||
"slot_list.go",
|
||||
"symlink.go",
|
||||
],
|
||||
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs",
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
package kernfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
@@ -26,7 +28,10 @@ import (
|
||||
// DynamicBytesFile implements kernfs.Inode and represents a read-only
|
||||
// file whose contents are backed by a vfs.DynamicBytesSource.
|
||||
//
|
||||
// Must be initialized with Init before first use.
|
||||
// Must be instantiated with NewDynamicBytesFile or initialized with Init
|
||||
// before first use.
|
||||
//
|
||||
// +stateify savable
|
||||
type DynamicBytesFile struct {
|
||||
InodeAttrs
|
||||
InodeNoopRefCount
|
||||
@@ -36,9 +41,14 @@ type DynamicBytesFile struct {
|
||||
data vfs.DynamicBytesSource
|
||||
}
|
||||
|
||||
// Init intializes a dynamic bytes file.
|
||||
func (f *DynamicBytesFile) Init(creds *auth.Credentials, ino uint64, data vfs.DynamicBytesSource) {
|
||||
f.InodeAttrs.Init(creds, ino, linux.ModeRegular|0444)
|
||||
var _ Inode = (*DynamicBytesFile)(nil)
|
||||
|
||||
// Init initializes a dynamic bytes file.
|
||||
func (f *DynamicBytesFile) Init(creds *auth.Credentials, ino uint64, data vfs.DynamicBytesSource, perm linux.FileMode) {
|
||||
if perm&^linux.PermissionsMask != 0 {
|
||||
panic(fmt.Sprintf("Only permission mask must be set: %x", perm&linux.PermissionsMask))
|
||||
}
|
||||
f.InodeAttrs.Init(creds, ino, linux.ModeRegular|perm)
|
||||
f.data = data
|
||||
}
|
||||
|
||||
@@ -59,6 +69,8 @@ func (f *DynamicBytesFile) SetStat(*vfs.Filesystem, vfs.SetStatOptions) error {
|
||||
// DynamicBytesFile.
|
||||
//
|
||||
// Must be initialized with Init before first use.
|
||||
//
|
||||
// +stateify savable
|
||||
type DynamicBytesFD struct {
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
vfs.DynamicBytesFileDescriptionImpl
|
||||
|
||||
@@ -154,7 +154,10 @@ func (fd *GenericDirectoryFD) IterDirents(ctx context.Context, cb vfs.IterDirent
|
||||
fd.off++
|
||||
}
|
||||
|
||||
return nil
|
||||
var err error
|
||||
relOffset := fd.off - int64(len(fd.children.set)) - 2
|
||||
fd.off, err = fd.inode().IterDirents(ctx, cb, fd.off, relOffset)
|
||||
return err
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDecriptionImpl.Seek.
|
||||
|
||||
@@ -139,6 +139,11 @@ func (*InodeNotDirectory) Lookup(ctx context.Context, name string) (*vfs.Dentry,
|
||||
panic("Lookup called on non-directory inode")
|
||||
}
|
||||
|
||||
// IterDirents implements Inode.IterDirents.
|
||||
func (*InodeNotDirectory) IterDirents(ctx context.Context, callback vfs.IterDirentsCallback, offset, relOffset int64) (newOffset int64, err error) {
|
||||
panic("IterDirents called on non-directory inode")
|
||||
}
|
||||
|
||||
// Valid implements Inode.Valid.
|
||||
func (*InodeNotDirectory) Valid(context.Context) bool {
|
||||
return true
|
||||
@@ -156,6 +161,11 @@ func (*InodeNoDynamicLookup) Lookup(ctx context.Context, name string) (*vfs.Dent
|
||||
return nil, syserror.ENOENT
|
||||
}
|
||||
|
||||
// IterDirents implements Inode.IterDirents.
|
||||
func (*InodeNoDynamicLookup) IterDirents(ctx context.Context, callback vfs.IterDirentsCallback, offset, relOffset int64) (int64, error) {
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// Valid implements Inode.Valid.
|
||||
func (*InodeNoDynamicLookup) Valid(ctx context.Context) bool {
|
||||
return true
|
||||
@@ -490,3 +500,13 @@ func (o *OrderedChildren) nthLocked(i int64) *slot {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// InodeSymlink partially implements Inode interface for symlinks.
|
||||
type InodeSymlink struct {
|
||||
InodeNotDirectory
|
||||
}
|
||||
|
||||
// Open implements Inode.Open.
|
||||
func (InodeSymlink) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
|
||||
return nil, syserror.ELOOP
|
||||
}
|
||||
|
||||
@@ -404,6 +404,15 @@ type inodeDynamicLookup interface {
|
||||
// Valid should return true if this inode is still valid, or needs to
|
||||
// be resolved again by a call to Lookup.
|
||||
Valid(ctx context.Context) bool
|
||||
|
||||
// IterDirents is used to iterate over dynamically created entries. It invokes
|
||||
// cb on each entry in the directory represented by the FileDescription.
|
||||
// 'offset' is the offset for the entire IterDirents call, which may include
|
||||
// results from the caller. 'relOffset' is the offset inside the entries
|
||||
// returned by this IterDirents invocation. In other words,
|
||||
// 'offset+relOffset+1' is the value that should be set in vfs.Dirent.NextOff,
|
||||
// while 'relOffset' is the place where iteration should start from.
|
||||
IterDirents(ctx context.Context, callback vfs.IterDirentsCallback, offset, relOffset int64) (newOffset int64, err error)
|
||||
}
|
||||
|
||||
type inodeSymlink interface {
|
||||
|
||||
@@ -133,7 +133,7 @@ type file struct {
|
||||
func (fs *filesystem) newFile(creds *auth.Credentials, content string) *kernfs.Dentry {
|
||||
f := &file{}
|
||||
f.content = content
|
||||
f.DynamicBytesFile.Init(creds, fs.NextIno(), f)
|
||||
f.DynamicBytesFile.Init(creds, fs.NextIno(), f, 0777)
|
||||
|
||||
d := &kernfs.Dentry{}
|
||||
d.Init(f)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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 kernfs
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
)
|
||||
|
||||
type staticSymlink struct {
|
||||
InodeAttrs
|
||||
InodeNoopRefCount
|
||||
InodeSymlink
|
||||
|
||||
target string
|
||||
}
|
||||
|
||||
var _ Inode = (*staticSymlink)(nil)
|
||||
|
||||
// NewStaticSymlink creates a new symlink file pointing to 'target'.
|
||||
func NewStaticSymlink(creds *auth.Credentials, ino uint64, perm linux.FileMode, target string) *Dentry {
|
||||
inode := &staticSymlink{target: target}
|
||||
inode.Init(creds, ino, linux.ModeSymlink|perm)
|
||||
|
||||
d := &Dentry{}
|
||||
d.Init(inode)
|
||||
return d
|
||||
}
|
||||
|
||||
func (s *staticSymlink) Readlink(_ context.Context) (string, error) {
|
||||
return s.target, nil
|
||||
}
|
||||
@@ -6,15 +6,17 @@ package(licenses = ["notice"])
|
||||
go_library(
|
||||
name = "proc",
|
||||
srcs = [
|
||||
"filesystems.go",
|
||||
"filesystem.go",
|
||||
"loadavg.go",
|
||||
"meminfo.go",
|
||||
"mounts.go",
|
||||
"net.go",
|
||||
"proc.go",
|
||||
"stat.go",
|
||||
"sys.go",
|
||||
"task.go",
|
||||
"task_files.go",
|
||||
"tasks.go",
|
||||
"tasks_files.go",
|
||||
"version.go",
|
||||
],
|
||||
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/proc",
|
||||
@@ -24,8 +26,10 @@ go_library(
|
||||
"//pkg/log",
|
||||
"//pkg/sentry/context",
|
||||
"//pkg/sentry/fs",
|
||||
"//pkg/sentry/fsimpl/kernfs",
|
||||
"//pkg/sentry/inet",
|
||||
"//pkg/sentry/kernel",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/limits",
|
||||
"//pkg/sentry/mm",
|
||||
"//pkg/sentry/socket",
|
||||
@@ -34,17 +38,40 @@ go_library(
|
||||
"//pkg/sentry/usage",
|
||||
"//pkg/sentry/usermem",
|
||||
"//pkg/sentry/vfs",
|
||||
"//pkg/syserror",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "proc_test",
|
||||
size = "small",
|
||||
srcs = ["net_test.go"],
|
||||
srcs = [
|
||||
"boot_test.go",
|
||||
"net_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/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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// 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 proc
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/cpuid"
|
||||
"gvisor.dev/gvisor/pkg/memutil"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/sched"
|
||||
"gvisor.dev/gvisor/pkg/sentry/limits"
|
||||
"gvisor.dev/gvisor/pkg/sentry/loader"
|
||||
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
|
||||
"gvisor.dev/gvisor/pkg/sentry/platform"
|
||||
"gvisor.dev/gvisor/pkg/sentry/time"
|
||||
|
||||
// Platforms are plugable.
|
||||
_ "gvisor.dev/gvisor/pkg/sentry/platform/kvm"
|
||||
_ "gvisor.dev/gvisor/pkg/sentry/platform/ptrace"
|
||||
)
|
||||
|
||||
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) {
|
||||
platformCtr, err := platform.Lookup(*platformFlag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("platform not found: %v", err)
|
||||
}
|
||||
deviceFile, err := platformCtr.OpenDevice()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating platform: %v", err)
|
||||
}
|
||||
plat, err := platformCtr.New(deviceFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating platform: %v", err)
|
||||
}
|
||||
|
||||
k := &kernel.Kernel{
|
||||
Platform: plat,
|
||||
}
|
||||
|
||||
mf, err := createMemoryFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k.SetMemoryFile(mf)
|
||||
|
||||
// Pass k as the platform since it is savable, unlike the actual platform.
|
||||
vdso, err := loader.PrepareVDSO(nil, k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating vdso: %v", err)
|
||||
}
|
||||
|
||||
// Create timekeeper.
|
||||
tk, err := kernel.NewTimekeeper(k, vdso.ParamPage.FileRange())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating timekeeper: %v", err)
|
||||
}
|
||||
tk.SetClocks(time.NewCalibratedClocks())
|
||||
|
||||
creds := auth.NewRootCredentials(auth.NewRootUserNamespace())
|
||||
|
||||
// Initiate the Kernel object, which is required by the Context passed
|
||||
// to createVFS in order to mount (among other things) procfs.
|
||||
if err = k.Init(kernel.InitKernelArgs{
|
||||
ApplicationCores: uint(runtime.GOMAXPROCS(-1)),
|
||||
FeatureSet: cpuid.HostFeatureSet(),
|
||||
Timekeeper: tk,
|
||||
RootUserNamespace: creds.UserNamespace,
|
||||
Vdso: vdso,
|
||||
RootUTSNamespace: kernel.NewUTSNamespace("hostname", "domain", creds.UserNamespace),
|
||||
RootIPCNamespace: kernel.NewIPCNamespace(creds.UserNamespace),
|
||||
RootAbstractSocketNamespace: kernel.NewAbstractSocketNamespace(),
|
||||
PIDNamespace: kernel.NewRootPIDNamespace(creds.UserNamespace),
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("initializing kernel: %v", err)
|
||||
}
|
||||
|
||||
ctx := k.SupervisorContext()
|
||||
|
||||
// Create mount namespace without root as it's the minimum required to create
|
||||
// the global thread group.
|
||||
mntns, err := fs.NewMountNamespace(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ls, err := limits.NewLinuxLimitSet()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tg := k.NewThreadGroup(mntns, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, ls)
|
||||
k.TestOnly_SetGlobalInit(tg)
|
||||
|
||||
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) {
|
||||
k := kernel.KernelFromContext(ctx)
|
||||
config := &kernel.TaskConfig{
|
||||
Kernel: k,
|
||||
ThreadGroup: tc,
|
||||
TaskContext: &kernel.TaskContext{Name: name},
|
||||
Credentials: auth.CredentialsFromContext(ctx),
|
||||
AllowedCPUMask: sched.NewFullCPUSet(k.ApplicationCores()),
|
||||
UTSNamespace: kernel.UTSNamespaceFromContext(ctx),
|
||||
IPCNamespace: kernel.IPCNamespaceFromContext(ctx),
|
||||
AbstractSocketNamespace: kernel.NewAbstractSocketNamespace(),
|
||||
}
|
||||
return k.TaskSet().NewTask(config)
|
||||
}
|
||||
|
||||
func createMemoryFile() (*pgalloc.MemoryFile, error) {
|
||||
const memfileName = "test-memory"
|
||||
memfd, err := memutil.CreateMemFD(memfileName, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating memfd: %v", err)
|
||||
}
|
||||
memfile := os.NewFile(uintptr(memfd), memfileName)
|
||||
mf, err := pgalloc.NewMemoryFile(memfile, pgalloc.MemoryFileOpts{})
|
||||
if err != nil {
|
||||
memfile.Close()
|
||||
return nil, fmt.Errorf("error creating pgalloc.MemoryFile: %v", err)
|
||||
}
|
||||
return mf, nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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 proc implements a partial in-memory file system for procfs.
|
||||
package proc
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// procFSType is the factory class for procfs.
|
||||
//
|
||||
// +stateify savable
|
||||
type procFSType struct{}
|
||||
|
||||
var _ vfs.FilesystemType = (*procFSType)(nil)
|
||||
|
||||
// GetFilesystem implements vfs.FilesystemType.
|
||||
func (ft *procFSType) GetFilesystem(ctx context.Context, vfsObj *vfs.VirtualFilesystem, creds *auth.Credentials, source string, opts vfs.GetFilesystemOptions) (*vfs.Filesystem, *vfs.Dentry, error) {
|
||||
k := kernel.KernelFromContext(ctx)
|
||||
if k == nil {
|
||||
return nil, nil, fmt.Errorf("procfs requires a kernel")
|
||||
}
|
||||
pidns := kernel.PIDNamespaceFromContext(ctx)
|
||||
if pidns == nil {
|
||||
return nil, nil, fmt.Errorf("procfs requires a PID namespace")
|
||||
}
|
||||
|
||||
procfs := &kernfs.Filesystem{}
|
||||
procfs.VFSFilesystem().Init(vfsObj, procfs)
|
||||
|
||||
_, dentry := newTasksInode(procfs, k, pidns)
|
||||
return procfs.VFSFilesystem(), dentry.VFSDentry(), nil
|
||||
}
|
||||
|
||||
// dynamicInode is an overfitted interface for common Inodes with
|
||||
// dynamicByteSource types used in procfs.
|
||||
type dynamicInode interface {
|
||||
kernfs.Inode
|
||||
vfs.DynamicBytesSource
|
||||
|
||||
Init(creds *auth.Credentials, ino uint64, data vfs.DynamicBytesSource, perm linux.FileMode)
|
||||
}
|
||||
|
||||
func newDentry(creds *auth.Credentials, ino uint64, perm linux.FileMode, inode dynamicInode) *kernfs.Dentry {
|
||||
inode.Init(creds, ino, inode, perm)
|
||||
|
||||
d := &kernfs.Dentry{}
|
||||
d.Init(inode)
|
||||
return d
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// 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 proc
|
||||
|
||||
// filesystemsData implements vfs.DynamicBytesSource for /proc/filesystems.
|
||||
//
|
||||
// +stateify savable
|
||||
type filesystemsData struct{}
|
||||
|
||||
// TODO(gvisor.dev/issue/1195): Implement vfs.DynamicBytesSource.Generate for
|
||||
// filesystemsData. We would need to retrive filesystem names from
|
||||
// vfs.VirtualFilesystem. Also needs vfs replacement for
|
||||
// fs.Filesystem.AllowUserList() and fs.FilesystemRequiresDev.
|
||||
@@ -19,15 +19,17 @@ import (
|
||||
"fmt"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
||||
)
|
||||
|
||||
// loadavgData backs /proc/loadavg.
|
||||
//
|
||||
// +stateify savable
|
||||
type loadavgData struct{}
|
||||
type loadavgData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
}
|
||||
|
||||
var _ vfs.DynamicBytesSource = (*loadavgData)(nil)
|
||||
var _ dynamicInode = (*loadavgData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (d *loadavgData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
|
||||
@@ -19,21 +19,23 @@ import (
|
||||
"fmt"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usage"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
)
|
||||
|
||||
// meminfoData implements vfs.DynamicBytesSource for /proc/meminfo.
|
||||
//
|
||||
// +stateify savable
|
||||
type meminfoData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
// k is the owning Kernel.
|
||||
k *kernel.Kernel
|
||||
}
|
||||
|
||||
var _ vfs.DynamicBytesSource = (*meminfoData)(nil)
|
||||
var _ dynamicInode = (*meminfoData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (d *meminfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// 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 proc implements a partial in-memory file system for procfs.
|
||||
package proc
|
||||
@@ -20,8 +20,8 @@ import (
|
||||
|
||||
"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/vfs"
|
||||
)
|
||||
|
||||
// cpuStats contains the breakdown of CPU time for /proc/stat.
|
||||
@@ -66,11 +66,13 @@ func (c cpuStats) String() string {
|
||||
//
|
||||
// +stateify savable
|
||||
type statData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
// k is the owning Kernel.
|
||||
k *kernel.Kernel
|
||||
}
|
||||
|
||||
var _ vfs.DynamicBytesSource = (*statData)(nil)
|
||||
var _ dynamicInode = (*statData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (s *statData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
|
||||
+142
-217
@@ -15,251 +15,176 @@
|
||||
package proc
|
||||
|
||||
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/limits"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/mm"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usage"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// mapsCommon is embedded by mapsData and smapsData.
|
||||
type mapsCommon struct {
|
||||
t *kernel.Task
|
||||
}
|
||||
|
||||
// mm gets the kernel task's MemoryManager. No additional reference is taken on
|
||||
// mm here. This is safe because MemoryManager.destroy is required to leave the
|
||||
// MemoryManager in a state where it's still usable as a DynamicBytesSource.
|
||||
func (md *mapsCommon) mm() *mm.MemoryManager {
|
||||
var tmm *mm.MemoryManager
|
||||
md.t.WithMuLocked(func(t *kernel.Task) {
|
||||
if mm := t.MemoryManager(); mm != nil {
|
||||
tmm = mm
|
||||
}
|
||||
})
|
||||
return tmm
|
||||
}
|
||||
|
||||
// mapsData implements vfs.DynamicBytesSource for /proc/[pid]/maps.
|
||||
// taskInode represents the inode for /proc/PID/ directory.
|
||||
//
|
||||
// +stateify savable
|
||||
type mapsData struct {
|
||||
mapsCommon
|
||||
type taskInode struct {
|
||||
kernfs.InodeNotSymlink
|
||||
kernfs.InodeDirectoryNoNewChildren
|
||||
kernfs.InodeNoDynamicLookup
|
||||
kernfs.InodeAttrs
|
||||
kernfs.OrderedChildren
|
||||
|
||||
task *kernel.Task
|
||||
}
|
||||
|
||||
var _ vfs.DynamicBytesSource = (*mapsData)(nil)
|
||||
var _ kernfs.Inode = (*taskInode)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (md *mapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
if mm := md.mm(); mm != nil {
|
||||
mm.ReadMapsDataInto(ctx, buf)
|
||||
func newTaskInode(inoGen InoGenerator, task *kernel.Task, pidns *kernel.PIDNamespace, isThreadGroup bool) *kernfs.Dentry {
|
||||
contents := map[string]*kernfs.Dentry{
|
||||
//"auxv": newAuxvec(t, msrc),
|
||||
//"cmdline": newExecArgInode(t, msrc, cmdlineExecArg),
|
||||
//"comm": newComm(t, msrc),
|
||||
//"environ": newExecArgInode(t, msrc, environExecArg),
|
||||
//"exe": newExe(t, msrc),
|
||||
//"fd": newFdDir(t, msrc),
|
||||
//"fdinfo": newFdInfoDir(t, msrc),
|
||||
//"gid_map": newGIDMap(t, msrc),
|
||||
"io": newTaskOwnedFile(task, inoGen.NextIno(), defaultPermission, newIO(task, isThreadGroup)),
|
||||
"maps": newTaskOwnedFile(task, inoGen.NextIno(), defaultPermission, &mapsData{task: task}),
|
||||
//"mountinfo": seqfile.NewSeqFileInode(t, &mountInfoFile{t: t}, msrc),
|
||||
//"mounts": seqfile.NewSeqFileInode(t, &mountsFile{t: t}, msrc),
|
||||
//"ns": newNamespaceDir(t, msrc),
|
||||
"smaps": newTaskOwnedFile(task, inoGen.NextIno(), defaultPermission, &smapsData{task: task}),
|
||||
"stat": newTaskOwnedFile(task, inoGen.NextIno(), defaultPermission, &taskStatData{t: task, pidns: pidns, tgstats: isThreadGroup}),
|
||||
"statm": newTaskOwnedFile(task, inoGen.NextIno(), defaultPermission, &statmData{t: task}),
|
||||
"status": newTaskOwnedFile(task, inoGen.NextIno(), defaultPermission, &statusData{t: task, pidns: pidns}),
|
||||
//"uid_map": newUIDMap(t, msrc),
|
||||
}
|
||||
if isThreadGroup {
|
||||
//contents["task"] = p.newSubtasks(t, msrc)
|
||||
}
|
||||
//if len(p.cgroupControllers) > 0 {
|
||||
// contents["cgroup"] = newCGroupInode(t, msrc, p.cgroupControllers)
|
||||
//}
|
||||
|
||||
taskInode := &taskInode{task: task}
|
||||
// Note: credentials are overridden by taskOwnedInode.
|
||||
taskInode.InodeAttrs.Init(task.Credentials(), inoGen.NextIno(), linux.ModeDirectory|0555)
|
||||
|
||||
inode := &taskOwnedInode{Inode: taskInode, owner: task}
|
||||
dentry := &kernfs.Dentry{}
|
||||
dentry.Init(inode)
|
||||
|
||||
taskInode.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
|
||||
links := taskInode.OrderedChildren.Populate(dentry, contents)
|
||||
taskInode.IncLinks(links)
|
||||
|
||||
return dentry
|
||||
}
|
||||
|
||||
// Valid implements kernfs.inodeDynamicLookup. This inode remains valid as long
|
||||
// as the task is still running. When it's dead, another tasks with the same
|
||||
// PID could replace it.
|
||||
func (i *taskInode) Valid(ctx context.Context) bool {
|
||||
return i.task.ExitState() != kernel.TaskExitDead
|
||||
}
|
||||
|
||||
// Open implements kernfs.Inode.
|
||||
func (i *taskInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, flags)
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
// SetStat implements kernfs.Inode.
|
||||
func (i *taskInode) SetStat(_ *vfs.Filesystem, opts vfs.SetStatOptions) error {
|
||||
stat := opts.Stat
|
||||
if stat.Mask&linux.STATX_MODE != 0 {
|
||||
return syserror.EPERM
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// smapsData implements vfs.DynamicBytesSource for /proc/[pid]/smaps.
|
||||
//
|
||||
// +stateify savable
|
||||
type smapsData struct {
|
||||
mapsCommon
|
||||
// taskOwnedInode implements kernfs.Inode and overrides inode owner with task
|
||||
// effective user and group.
|
||||
type taskOwnedInode struct {
|
||||
kernfs.Inode
|
||||
|
||||
// owner is the task that owns this inode.
|
||||
owner *kernel.Task
|
||||
}
|
||||
|
||||
var _ vfs.DynamicBytesSource = (*smapsData)(nil)
|
||||
var _ kernfs.Inode = (*taskOwnedInode)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (sd *smapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
if mm := sd.mm(); mm != nil {
|
||||
mm.ReadSmapsDataInto(ctx, buf)
|
||||
}
|
||||
return nil
|
||||
func newTaskOwnedFile(task *kernel.Task, ino uint64, perm linux.FileMode, inode dynamicInode) *kernfs.Dentry {
|
||||
// Note: credentials are overridden by taskOwnedInode.
|
||||
inode.Init(task.Credentials(), ino, inode, perm)
|
||||
|
||||
taskInode := &taskOwnedInode{Inode: inode, owner: task}
|
||||
d := &kernfs.Dentry{}
|
||||
d.Init(taskInode)
|
||||
return d
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type taskStatData struct {
|
||||
t *kernel.Task
|
||||
|
||||
// If tgstats is true, accumulate fault stats (not implemented) and CPU
|
||||
// time across all tasks in t's thread group.
|
||||
tgstats bool
|
||||
|
||||
// pidns is the PID namespace associated with the proc filesystem that
|
||||
// includes the file using this statData.
|
||||
pidns *kernel.PIDNamespace
|
||||
// Stat implements kernfs.Inode.
|
||||
func (i *taskOwnedInode) Stat(fs *vfs.Filesystem) linux.Statx {
|
||||
stat := i.Inode.Stat(fs)
|
||||
uid, gid := i.getOwner(linux.FileMode(stat.Mode))
|
||||
stat.UID = uint32(uid)
|
||||
stat.GID = uint32(gid)
|
||||
return stat
|
||||
}
|
||||
|
||||
var _ vfs.DynamicBytesSource = (*taskStatData)(nil)
|
||||
// CheckPermissions implements kernfs.Inode.
|
||||
func (i *taskOwnedInode) CheckPermissions(creds *auth.Credentials, ats vfs.AccessTypes) error {
|
||||
mode := i.Mode()
|
||||
uid, gid := i.getOwner(mode)
|
||||
return vfs.GenericCheckPermissions(
|
||||
creds,
|
||||
ats,
|
||||
mode.FileType() == linux.ModeDirectory,
|
||||
uint16(mode),
|
||||
uid,
|
||||
gid,
|
||||
)
|
||||
}
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (s *taskStatData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
fmt.Fprintf(buf, "%d ", s.pidns.IDOfTask(s.t))
|
||||
fmt.Fprintf(buf, "(%s) ", s.t.Name())
|
||||
fmt.Fprintf(buf, "%c ", s.t.StateStatus()[0])
|
||||
ppid := kernel.ThreadID(0)
|
||||
if parent := s.t.Parent(); parent != nil {
|
||||
ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup())
|
||||
func (i *taskOwnedInode) getOwner(mode linux.FileMode) (auth.KUID, auth.KGID) {
|
||||
// By default, set the task owner as the file owner.
|
||||
creds := i.owner.Credentials()
|
||||
uid := creds.EffectiveKUID
|
||||
gid := creds.EffectiveKGID
|
||||
|
||||
// Linux doesn't apply dumpability adjustments to world readable/executable
|
||||
// directories so that applications can stat /proc/PID to determine the
|
||||
// effective UID of a process. See fs/proc/base.c:task_dump_owner.
|
||||
if mode.FileType() == linux.ModeDirectory && mode.Permissions() == 0555 {
|
||||
return uid, gid
|
||||
}
|
||||
fmt.Fprintf(buf, "%d ", ppid)
|
||||
fmt.Fprintf(buf, "%d ", s.pidns.IDOfProcessGroup(s.t.ThreadGroup().ProcessGroup()))
|
||||
fmt.Fprintf(buf, "%d ", s.pidns.IDOfSession(s.t.ThreadGroup().Session()))
|
||||
fmt.Fprintf(buf, "0 0 " /* tty_nr tpgid */)
|
||||
fmt.Fprintf(buf, "0 " /* flags */)
|
||||
fmt.Fprintf(buf, "0 0 0 0 " /* minflt cminflt majflt cmajflt */)
|
||||
var cputime usage.CPUStats
|
||||
if s.tgstats {
|
||||
cputime = s.t.ThreadGroup().CPUStats()
|
||||
} else {
|
||||
cputime = s.t.CPUStats()
|
||||
|
||||
// If the task is not dumpable, then root (in the namespace preferred)
|
||||
// owns the file.
|
||||
m := getMM(i.owner)
|
||||
if m == nil {
|
||||
return auth.RootKUID, auth.RootKGID
|
||||
}
|
||||
fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime))
|
||||
cputime = s.t.ThreadGroup().JoinedChildCPUStats()
|
||||
fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime))
|
||||
fmt.Fprintf(buf, "%d %d ", s.t.Priority(), s.t.Niceness())
|
||||
fmt.Fprintf(buf, "%d ", s.t.ThreadGroup().Count())
|
||||
|
||||
// itrealvalue. Since kernel 2.6.17, this field is no longer
|
||||
// maintained, and is hard coded as 0.
|
||||
fmt.Fprintf(buf, "0 ")
|
||||
|
||||
// Start time is relative to boot time, expressed in clock ticks.
|
||||
fmt.Fprintf(buf, "%d ", linux.ClockTFromDuration(s.t.StartTime().Sub(s.t.Kernel().Timekeeper().BootTime())))
|
||||
|
||||
var vss, rss uint64
|
||||
s.t.WithMuLocked(func(t *kernel.Task) {
|
||||
if mm := t.MemoryManager(); mm != nil {
|
||||
vss = mm.VirtualMemorySize()
|
||||
rss = mm.ResidentSetSize()
|
||||
if m.Dumpability() != mm.UserDumpable {
|
||||
uid = auth.RootKUID
|
||||
if kuid := creds.UserNamespace.MapToKUID(auth.RootUID); kuid.Ok() {
|
||||
uid = kuid
|
||||
}
|
||||
})
|
||||
fmt.Fprintf(buf, "%d %d ", vss, rss/usermem.PageSize)
|
||||
|
||||
// rsslim.
|
||||
fmt.Fprintf(buf, "%d ", s.t.ThreadGroup().Limits().Get(limits.Rss).Cur)
|
||||
|
||||
fmt.Fprintf(buf, "0 0 0 0 0 " /* startcode endcode startstack kstkesp kstkeip */)
|
||||
fmt.Fprintf(buf, "0 0 0 0 0 " /* signal blocked sigignore sigcatch wchan */)
|
||||
fmt.Fprintf(buf, "0 0 " /* nswap cnswap */)
|
||||
terminationSignal := linux.Signal(0)
|
||||
if s.t == s.t.ThreadGroup().Leader() {
|
||||
terminationSignal = s.t.ThreadGroup().TerminationSignal()
|
||||
}
|
||||
fmt.Fprintf(buf, "%d ", terminationSignal)
|
||||
fmt.Fprintf(buf, "0 0 0 " /* processor rt_priority policy */)
|
||||
fmt.Fprintf(buf, "0 0 0 " /* delayacct_blkio_ticks guest_time cguest_time */)
|
||||
fmt.Fprintf(buf, "0 0 0 0 0 0 0 " /* start_data end_data start_brk arg_start arg_end env_start env_end */)
|
||||
fmt.Fprintf(buf, "0\n" /* exit_code */)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// statmData implements vfs.DynamicBytesSource for /proc/[pid]/statm.
|
||||
//
|
||||
// +stateify savable
|
||||
type statmData struct {
|
||||
t *kernel.Task
|
||||
}
|
||||
|
||||
var _ vfs.DynamicBytesSource = (*statmData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (s *statmData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
var vss, rss uint64
|
||||
s.t.WithMuLocked(func(t *kernel.Task) {
|
||||
if mm := t.MemoryManager(); mm != nil {
|
||||
vss = mm.VirtualMemorySize()
|
||||
rss = mm.ResidentSetSize()
|
||||
gid = auth.RootKGID
|
||||
if kgid := creds.UserNamespace.MapToKGID(auth.RootGID); kgid.Ok() {
|
||||
gid = kgid
|
||||
}
|
||||
})
|
||||
|
||||
fmt.Fprintf(buf, "%d %d 0 0 0 0 0\n", vss/usermem.PageSize, rss/usermem.PageSize)
|
||||
return nil
|
||||
}
|
||||
|
||||
// statusData implements vfs.DynamicBytesSource for /proc/[pid]/status.
|
||||
//
|
||||
// +stateify savable
|
||||
type statusData struct {
|
||||
t *kernel.Task
|
||||
pidns *kernel.PIDNamespace
|
||||
}
|
||||
|
||||
var _ vfs.DynamicBytesSource = (*statusData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (s *statusData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
fmt.Fprintf(buf, "Name:\t%s\n", s.t.Name())
|
||||
fmt.Fprintf(buf, "State:\t%s\n", s.t.StateStatus())
|
||||
fmt.Fprintf(buf, "Tgid:\t%d\n", s.pidns.IDOfThreadGroup(s.t.ThreadGroup()))
|
||||
fmt.Fprintf(buf, "Pid:\t%d\n", s.pidns.IDOfTask(s.t))
|
||||
ppid := kernel.ThreadID(0)
|
||||
if parent := s.t.Parent(); parent != nil {
|
||||
ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup())
|
||||
}
|
||||
fmt.Fprintf(buf, "PPid:\t%d\n", ppid)
|
||||
tpid := kernel.ThreadID(0)
|
||||
if tracer := s.t.Tracer(); tracer != nil {
|
||||
tpid = s.pidns.IDOfTask(tracer)
|
||||
return uid, gid
|
||||
}
|
||||
|
||||
func newIO(t *kernel.Task, isThreadGroup bool) *ioData {
|
||||
if isThreadGroup {
|
||||
return &ioData{ioUsage: t.ThreadGroup()}
|
||||
}
|
||||
fmt.Fprintf(buf, "TracerPid:\t%d\n", tpid)
|
||||
var fds int
|
||||
var vss, rss, data uint64
|
||||
s.t.WithMuLocked(func(t *kernel.Task) {
|
||||
if fdTable := t.FDTable(); fdTable != nil {
|
||||
fds = fdTable.Size()
|
||||
}
|
||||
if mm := t.MemoryManager(); mm != nil {
|
||||
vss = mm.VirtualMemorySize()
|
||||
rss = mm.ResidentSetSize()
|
||||
data = mm.VirtualDataSize()
|
||||
}
|
||||
})
|
||||
fmt.Fprintf(buf, "FDSize:\t%d\n", fds)
|
||||
fmt.Fprintf(buf, "VmSize:\t%d kB\n", vss>>10)
|
||||
fmt.Fprintf(buf, "VmRSS:\t%d kB\n", rss>>10)
|
||||
fmt.Fprintf(buf, "VmData:\t%d kB\n", data>>10)
|
||||
fmt.Fprintf(buf, "Threads:\t%d\n", s.t.ThreadGroup().Count())
|
||||
creds := s.t.Credentials()
|
||||
fmt.Fprintf(buf, "CapInh:\t%016x\n", creds.InheritableCaps)
|
||||
fmt.Fprintf(buf, "CapPrm:\t%016x\n", creds.PermittedCaps)
|
||||
fmt.Fprintf(buf, "CapEff:\t%016x\n", creds.EffectiveCaps)
|
||||
fmt.Fprintf(buf, "CapBnd:\t%016x\n", creds.BoundingCaps)
|
||||
fmt.Fprintf(buf, "Seccomp:\t%d\n", s.t.SeccompMode())
|
||||
// We unconditionally report a single NUMA node. See
|
||||
// pkg/sentry/syscalls/linux/sys_mempolicy.go.
|
||||
fmt.Fprintf(buf, "Mems_allowed:\t1\n")
|
||||
fmt.Fprintf(buf, "Mems_allowed_list:\t0\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ioUsage is the /proc/<pid>/io and /proc/<pid>/task/<tid>/io data provider.
|
||||
type ioUsage interface {
|
||||
// IOUsage returns the io usage data.
|
||||
IOUsage() *usage.IO
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type ioData struct {
|
||||
ioUsage
|
||||
}
|
||||
|
||||
var _ vfs.DynamicBytesSource = (*ioData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (i *ioData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
io := usage.IO{}
|
||||
io.Accumulate(i.IOUsage())
|
||||
|
||||
fmt.Fprintf(buf, "char: %d\n", io.CharsRead)
|
||||
fmt.Fprintf(buf, "wchar: %d\n", io.CharsWritten)
|
||||
fmt.Fprintf(buf, "syscr: %d\n", io.ReadSyscalls)
|
||||
fmt.Fprintf(buf, "syscw: %d\n", io.WriteSyscalls)
|
||||
fmt.Fprintf(buf, "read_bytes: %d\n", io.BytesRead)
|
||||
fmt.Fprintf(buf, "write_bytes: %d\n", io.BytesWritten)
|
||||
fmt.Fprintf(buf, "cancelled_write_bytes: %d\n", io.BytesWriteCancelled)
|
||||
return nil
|
||||
return &ioData{ioUsage: t}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
// 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 proc
|
||||
|
||||
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/limits"
|
||||
"gvisor.dev/gvisor/pkg/sentry/mm"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usage"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
)
|
||||
|
||||
// mm gets the kernel task's MemoryManager. No additional reference is taken on
|
||||
// mm here. This is safe because MemoryManager.destroy is required to leave the
|
||||
// MemoryManager in a state where it's still usable as a DynamicBytesSource.
|
||||
func getMM(task *kernel.Task) *mm.MemoryManager {
|
||||
var tmm *mm.MemoryManager
|
||||
task.WithMuLocked(func(t *kernel.Task) {
|
||||
if mm := t.MemoryManager(); mm != nil {
|
||||
tmm = mm
|
||||
}
|
||||
})
|
||||
return tmm
|
||||
}
|
||||
|
||||
// mapsData implements vfs.DynamicBytesSource for /proc/[pid]/maps.
|
||||
//
|
||||
// +stateify savable
|
||||
type mapsData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
task *kernel.Task
|
||||
}
|
||||
|
||||
var _ dynamicInode = (*mapsData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (d *mapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
if mm := getMM(d.task); mm != nil {
|
||||
mm.ReadMapsDataInto(ctx, buf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// smapsData implements vfs.DynamicBytesSource for /proc/[pid]/smaps.
|
||||
//
|
||||
// +stateify savable
|
||||
type smapsData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
task *kernel.Task
|
||||
}
|
||||
|
||||
var _ dynamicInode = (*smapsData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (d *smapsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
if mm := getMM(d.task); mm != nil {
|
||||
mm.ReadSmapsDataInto(ctx, buf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type taskStatData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
t *kernel.Task
|
||||
|
||||
// If tgstats is true, accumulate fault stats (not implemented) and CPU
|
||||
// time across all tasks in t's thread group.
|
||||
tgstats bool
|
||||
|
||||
// pidns is the PID namespace associated with the proc filesystem that
|
||||
// includes the file using this statData.
|
||||
pidns *kernel.PIDNamespace
|
||||
}
|
||||
|
||||
var _ dynamicInode = (*taskStatData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (s *taskStatData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
fmt.Fprintf(buf, "%d ", s.pidns.IDOfTask(s.t))
|
||||
fmt.Fprintf(buf, "(%s) ", s.t.Name())
|
||||
fmt.Fprintf(buf, "%c ", s.t.StateStatus()[0])
|
||||
ppid := kernel.ThreadID(0)
|
||||
if parent := s.t.Parent(); parent != nil {
|
||||
ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup())
|
||||
}
|
||||
fmt.Fprintf(buf, "%d ", ppid)
|
||||
fmt.Fprintf(buf, "%d ", s.pidns.IDOfProcessGroup(s.t.ThreadGroup().ProcessGroup()))
|
||||
fmt.Fprintf(buf, "%d ", s.pidns.IDOfSession(s.t.ThreadGroup().Session()))
|
||||
fmt.Fprintf(buf, "0 0 " /* tty_nr tpgid */)
|
||||
fmt.Fprintf(buf, "0 " /* flags */)
|
||||
fmt.Fprintf(buf, "0 0 0 0 " /* minflt cminflt majflt cmajflt */)
|
||||
var cputime usage.CPUStats
|
||||
if s.tgstats {
|
||||
cputime = s.t.ThreadGroup().CPUStats()
|
||||
} else {
|
||||
cputime = s.t.CPUStats()
|
||||
}
|
||||
fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime))
|
||||
cputime = s.t.ThreadGroup().JoinedChildCPUStats()
|
||||
fmt.Fprintf(buf, "%d %d ", linux.ClockTFromDuration(cputime.UserTime), linux.ClockTFromDuration(cputime.SysTime))
|
||||
fmt.Fprintf(buf, "%d %d ", s.t.Priority(), s.t.Niceness())
|
||||
fmt.Fprintf(buf, "%d ", s.t.ThreadGroup().Count())
|
||||
|
||||
// itrealvalue. Since kernel 2.6.17, this field is no longer
|
||||
// maintained, and is hard coded as 0.
|
||||
fmt.Fprintf(buf, "0 ")
|
||||
|
||||
// Start time is relative to boot time, expressed in clock ticks.
|
||||
fmt.Fprintf(buf, "%d ", linux.ClockTFromDuration(s.t.StartTime().Sub(s.t.Kernel().Timekeeper().BootTime())))
|
||||
|
||||
var vss, rss uint64
|
||||
s.t.WithMuLocked(func(t *kernel.Task) {
|
||||
if mm := t.MemoryManager(); mm != nil {
|
||||
vss = mm.VirtualMemorySize()
|
||||
rss = mm.ResidentSetSize()
|
||||
}
|
||||
})
|
||||
fmt.Fprintf(buf, "%d %d ", vss, rss/usermem.PageSize)
|
||||
|
||||
// rsslim.
|
||||
fmt.Fprintf(buf, "%d ", s.t.ThreadGroup().Limits().Get(limits.Rss).Cur)
|
||||
|
||||
fmt.Fprintf(buf, "0 0 0 0 0 " /* startcode endcode startstack kstkesp kstkeip */)
|
||||
fmt.Fprintf(buf, "0 0 0 0 0 " /* signal blocked sigignore sigcatch wchan */)
|
||||
fmt.Fprintf(buf, "0 0 " /* nswap cnswap */)
|
||||
terminationSignal := linux.Signal(0)
|
||||
if s.t == s.t.ThreadGroup().Leader() {
|
||||
terminationSignal = s.t.ThreadGroup().TerminationSignal()
|
||||
}
|
||||
fmt.Fprintf(buf, "%d ", terminationSignal)
|
||||
fmt.Fprintf(buf, "0 0 0 " /* processor rt_priority policy */)
|
||||
fmt.Fprintf(buf, "0 0 0 " /* delayacct_blkio_ticks guest_time cguest_time */)
|
||||
fmt.Fprintf(buf, "0 0 0 0 0 0 0 " /* start_data end_data start_brk arg_start arg_end env_start env_end */)
|
||||
fmt.Fprintf(buf, "0\n" /* exit_code */)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// statmData implements vfs.DynamicBytesSource for /proc/[pid]/statm.
|
||||
//
|
||||
// +stateify savable
|
||||
type statmData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
t *kernel.Task
|
||||
}
|
||||
|
||||
var _ dynamicInode = (*statmData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (s *statmData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
var vss, rss uint64
|
||||
s.t.WithMuLocked(func(t *kernel.Task) {
|
||||
if mm := t.MemoryManager(); mm != nil {
|
||||
vss = mm.VirtualMemorySize()
|
||||
rss = mm.ResidentSetSize()
|
||||
}
|
||||
})
|
||||
|
||||
fmt.Fprintf(buf, "%d %d 0 0 0 0 0\n", vss/usermem.PageSize, rss/usermem.PageSize)
|
||||
return nil
|
||||
}
|
||||
|
||||
// statusData implements vfs.DynamicBytesSource for /proc/[pid]/status.
|
||||
//
|
||||
// +stateify savable
|
||||
type statusData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
t *kernel.Task
|
||||
pidns *kernel.PIDNamespace
|
||||
}
|
||||
|
||||
var _ dynamicInode = (*statusData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (s *statusData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
fmt.Fprintf(buf, "Name:\t%s\n", s.t.Name())
|
||||
fmt.Fprintf(buf, "State:\t%s\n", s.t.StateStatus())
|
||||
fmt.Fprintf(buf, "Tgid:\t%d\n", s.pidns.IDOfThreadGroup(s.t.ThreadGroup()))
|
||||
fmt.Fprintf(buf, "Pid:\t%d\n", s.pidns.IDOfTask(s.t))
|
||||
ppid := kernel.ThreadID(0)
|
||||
if parent := s.t.Parent(); parent != nil {
|
||||
ppid = s.pidns.IDOfThreadGroup(parent.ThreadGroup())
|
||||
}
|
||||
fmt.Fprintf(buf, "PPid:\t%d\n", ppid)
|
||||
tpid := kernel.ThreadID(0)
|
||||
if tracer := s.t.Tracer(); tracer != nil {
|
||||
tpid = s.pidns.IDOfTask(tracer)
|
||||
}
|
||||
fmt.Fprintf(buf, "TracerPid:\t%d\n", tpid)
|
||||
var fds int
|
||||
var vss, rss, data uint64
|
||||
s.t.WithMuLocked(func(t *kernel.Task) {
|
||||
if fdTable := t.FDTable(); fdTable != nil {
|
||||
fds = fdTable.Size()
|
||||
}
|
||||
if mm := t.MemoryManager(); mm != nil {
|
||||
vss = mm.VirtualMemorySize()
|
||||
rss = mm.ResidentSetSize()
|
||||
data = mm.VirtualDataSize()
|
||||
}
|
||||
})
|
||||
fmt.Fprintf(buf, "FDSize:\t%d\n", fds)
|
||||
fmt.Fprintf(buf, "VmSize:\t%d kB\n", vss>>10)
|
||||
fmt.Fprintf(buf, "VmRSS:\t%d kB\n", rss>>10)
|
||||
fmt.Fprintf(buf, "VmData:\t%d kB\n", data>>10)
|
||||
fmt.Fprintf(buf, "Threads:\t%d\n", s.t.ThreadGroup().Count())
|
||||
creds := s.t.Credentials()
|
||||
fmt.Fprintf(buf, "CapInh:\t%016x\n", creds.InheritableCaps)
|
||||
fmt.Fprintf(buf, "CapPrm:\t%016x\n", creds.PermittedCaps)
|
||||
fmt.Fprintf(buf, "CapEff:\t%016x\n", creds.EffectiveCaps)
|
||||
fmt.Fprintf(buf, "CapBnd:\t%016x\n", creds.BoundingCaps)
|
||||
fmt.Fprintf(buf, "Seccomp:\t%d\n", s.t.SeccompMode())
|
||||
// We unconditionally report a single NUMA node. See
|
||||
// pkg/sentry/syscalls/linux/sys_mempolicy.go.
|
||||
fmt.Fprintf(buf, "Mems_allowed:\t1\n")
|
||||
fmt.Fprintf(buf, "Mems_allowed_list:\t0\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ioUsage is the /proc/<pid>/io and /proc/<pid>/task/<tid>/io data provider.
|
||||
type ioUsage interface {
|
||||
// IOUsage returns the io usage data.
|
||||
IOUsage() *usage.IO
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type ioData struct {
|
||||
kernfs.DynamicBytesFile
|
||||
|
||||
ioUsage
|
||||
}
|
||||
|
||||
var _ dynamicInode = (*ioData)(nil)
|
||||
|
||||
// Generate implements vfs.DynamicBytesSource.Generate.
|
||||
func (i *ioData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
||||
io := usage.IO{}
|
||||
io.Accumulate(i.IOUsage())
|
||||
|
||||
fmt.Fprintf(buf, "char: %d\n", io.CharsRead)
|
||||
fmt.Fprintf(buf, "wchar: %d\n", io.CharsWritten)
|
||||
fmt.Fprintf(buf, "syscr: %d\n", io.ReadSyscalls)
|
||||
fmt.Fprintf(buf, "syscw: %d\n", io.WriteSyscalls)
|
||||
fmt.Fprintf(buf, "read_bytes: %d\n", io.BytesRead)
|
||||
fmt.Fprintf(buf, "write_bytes: %d\n", io.BytesWritten)
|
||||
fmt.Fprintf(buf, "cancelled_write_bytes: %d\n", io.BytesWriteCancelled)
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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 proc
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
const defaultPermission = 0444
|
||||
|
||||
// InoGenerator generates unique inode numbers for a given filesystem.
|
||||
type InoGenerator interface {
|
||||
NextIno() uint64
|
||||
}
|
||||
|
||||
// tasksInode represents the inode for /proc/ directory.
|
||||
//
|
||||
// +stateify savable
|
||||
type tasksInode struct {
|
||||
kernfs.InodeNotSymlink
|
||||
kernfs.InodeDirectoryNoNewChildren
|
||||
kernfs.InodeAttrs
|
||||
kernfs.OrderedChildren
|
||||
|
||||
inoGen InoGenerator
|
||||
pidns *kernel.PIDNamespace
|
||||
}
|
||||
|
||||
var _ kernfs.Inode = (*tasksInode)(nil)
|
||||
|
||||
func newTasksInode(inoGen InoGenerator, k *kernel.Kernel, pidns *kernel.PIDNamespace) (*tasksInode, *kernfs.Dentry) {
|
||||
root := auth.NewRootCredentials(pidns.UserNamespace())
|
||||
contents := map[string]*kernfs.Dentry{
|
||||
//"cpuinfo": newCPUInfo(ctx, msrc),
|
||||
//"filesystems": seqfile.NewSeqFileInode(ctx, &filesystemsData{}, msrc),
|
||||
"loadavg": newDentry(root, inoGen.NextIno(), defaultPermission, &loadavgData{}),
|
||||
"meminfo": newDentry(root, inoGen.NextIno(), defaultPermission, &meminfoData{k: k}),
|
||||
"mounts": kernfs.NewStaticSymlink(root, inoGen.NextIno(), defaultPermission, "self/mounts"),
|
||||
"self": newSelfSymlink(root, inoGen.NextIno(), defaultPermission, pidns),
|
||||
"stat": newDentry(root, inoGen.NextIno(), defaultPermission, &statData{k: k}),
|
||||
"thread-self": newThreadSelfSymlink(root, inoGen.NextIno(), defaultPermission, pidns),
|
||||
//"uptime": newUptime(ctx, msrc),
|
||||
//"version": newVersionData(root, inoGen.NextIno(), k),
|
||||
"version": newDentry(root, inoGen.NextIno(), defaultPermission, &versionData{k: k}),
|
||||
}
|
||||
|
||||
inode := &tasksInode{
|
||||
pidns: pidns,
|
||||
inoGen: inoGen,
|
||||
}
|
||||
inode.InodeAttrs.Init(root, inoGen.NextIno(), linux.ModeDirectory|0555)
|
||||
|
||||
dentry := &kernfs.Dentry{}
|
||||
dentry.Init(inode)
|
||||
|
||||
inode.OrderedChildren.Init(kernfs.OrderedChildrenOptions{})
|
||||
links := inode.OrderedChildren.Populate(dentry, contents)
|
||||
inode.IncLinks(links)
|
||||
|
||||
return inode, dentry
|
||||
}
|
||||
|
||||
// Lookup implements kernfs.inodeDynamicLookup.
|
||||
func (i *tasksInode) Lookup(ctx context.Context, name string) (*vfs.Dentry, error) {
|
||||
// Try to lookup a corresponding task.
|
||||
tid, err := strconv.ParseUint(name, 10, 64)
|
||||
if err != nil {
|
||||
return nil, syserror.ENOENT
|
||||
}
|
||||
|
||||
task := i.pidns.TaskWithID(kernel.ThreadID(tid))
|
||||
if task == nil {
|
||||
return nil, syserror.ENOENT
|
||||
}
|
||||
|
||||
taskDentry := newTaskInode(i.inoGen, task, i.pidns, true)
|
||||
return taskDentry.VFSDentry(), nil
|
||||
}
|
||||
|
||||
// Valid implements kernfs.inodeDynamicLookup.
|
||||
func (i *tasksInode) Valid(ctx context.Context) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IterDirents implements kernfs.inodeDynamicLookup.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1195): Use tgid N offset = TGID_OFFSET + N.
|
||||
func (i *tasksInode) IterDirents(ctx context.Context, cb vfs.IterDirentsCallback, offset, relOffset int64) (int64, error) {
|
||||
var tids []int
|
||||
|
||||
// Collect all tasks. Per linux we only include it in directory listings if
|
||||
// it's the leader. But for whatever crazy reason, you can still walk to the
|
||||
// given node.
|
||||
for _, tg := range i.pidns.ThreadGroups() {
|
||||
if leader := tg.Leader(); leader != nil {
|
||||
tids = append(tids, int(i.pidns.IDOfThreadGroup(tg)))
|
||||
}
|
||||
}
|
||||
|
||||
if len(tids) == 0 {
|
||||
return offset, nil
|
||||
}
|
||||
if relOffset >= int64(len(tids)) {
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
sort.Ints(tids)
|
||||
for _, tid := range tids[relOffset:] {
|
||||
dirent := vfs.Dirent{
|
||||
Name: strconv.FormatUint(uint64(tid), 10),
|
||||
Type: linux.DT_DIR,
|
||||
Ino: i.inoGen.NextIno(),
|
||||
NextOff: offset + 1,
|
||||
}
|
||||
if !cb.Handle(dirent) {
|
||||
return offset, nil
|
||||
}
|
||||
offset++
|
||||
}
|
||||
return offset, nil
|
||||
}
|
||||
|
||||
// Open implements kernfs.Inode.
|
||||
func (i *tasksInode) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
|
||||
fd := &kernfs.GenericDirectoryFD{}
|
||||
fd.Init(rp.Mount(), vfsd, &i.OrderedChildren, flags)
|
||||
return fd.VFSFileDescription(), nil
|
||||
}
|
||||
|
||||
func (i *tasksInode) Stat(vsfs *vfs.Filesystem) linux.Statx {
|
||||
stat := i.InodeAttrs.Stat(vsfs)
|
||||
|
||||
// Add dynamic children to link count.
|
||||
for _, tg := range i.pidns.ThreadGroups() {
|
||||
if leader := tg.Leader(); leader != nil {
|
||||
stat.Nlink++
|
||||
}
|
||||
}
|
||||
|
||||
return stat
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// 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 proc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"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/syserror"
|
||||
)
|
||||
|
||||
type selfSymlink struct {
|
||||
kernfs.InodeAttrs
|
||||
kernfs.InodeNoopRefCount
|
||||
kernfs.InodeSymlink
|
||||
|
||||
pidns *kernel.PIDNamespace
|
||||
}
|
||||
|
||||
var _ kernfs.Inode = (*selfSymlink)(nil)
|
||||
|
||||
func newSelfSymlink(creds *auth.Credentials, ino uint64, perm linux.FileMode, pidns *kernel.PIDNamespace) *kernfs.Dentry {
|
||||
inode := &selfSymlink{pidns: pidns}
|
||||
inode.Init(creds, ino, linux.ModeSymlink|perm)
|
||||
|
||||
d := &kernfs.Dentry{}
|
||||
d.Init(inode)
|
||||
return d
|
||||
}
|
||||
|
||||
func (s *selfSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
t := kernel.TaskFromContext(ctx)
|
||||
if t == nil {
|
||||
// Who is reading this link?
|
||||
return "", syserror.EINVAL
|
||||
}
|
||||
tgid := s.pidns.IDOfThreadGroup(t.ThreadGroup())
|
||||
if tgid == 0 {
|
||||
return "", syserror.ENOENT
|
||||
}
|
||||
return strconv.FormatUint(uint64(tgid), 10), nil
|
||||
}
|
||||
|
||||
type threadSelfSymlink struct {
|
||||
kernfs.InodeAttrs
|
||||
kernfs.InodeNoopRefCount
|
||||
kernfs.InodeSymlink
|
||||
|
||||
pidns *kernel.PIDNamespace
|
||||
}
|
||||
|
||||
var _ kernfs.Inode = (*threadSelfSymlink)(nil)
|
||||
|
||||
func newThreadSelfSymlink(creds *auth.Credentials, ino uint64, perm linux.FileMode, pidns *kernel.PIDNamespace) *kernfs.Dentry {
|
||||
inode := &threadSelfSymlink{pidns: pidns}
|
||||
inode.Init(creds, ino, linux.ModeSymlink|perm)
|
||||
|
||||
d := &kernfs.Dentry{}
|
||||
d.Init(inode)
|
||||
return d
|
||||
}
|
||||
|
||||
func (s *threadSelfSymlink) Readlink(ctx context.Context) (string, error) {
|
||||
t := kernel.TaskFromContext(ctx)
|
||||
if t == nil {
|
||||
// Who is reading this link?
|
||||
return "", syserror.EINVAL
|
||||
}
|
||||
tgid := s.pidns.IDOfThreadGroup(t.ThreadGroup())
|
||||
tid := s.pidns.IDOfTask(t)
|
||||
if tid == 0 || tgid == 0 {
|
||||
return "", syserror.ENOENT
|
||||
}
|
||||
return fmt.Sprintf("%d/task/%d", tgid, tid), nil
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
// 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 proc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/fspath"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
type testIterDirentsCallback struct {
|
||||
dirents []vfs.Dirent
|
||||
}
|
||||
|
||||
func (t *testIterDirentsCallback) Handle(d vfs.Dirent) bool {
|
||||
t.dirents = append(t.dirents, d)
|
||||
return true
|
||||
}
|
||||
|
||||
func checkDots(dirs []vfs.Dirent) ([]vfs.Dirent, error) {
|
||||
if got := len(dirs); got < 2 {
|
||||
return dirs, fmt.Errorf("wrong number of dirents, want at least: 2, got: %d: %v", got, dirs)
|
||||
}
|
||||
for i, want := range []string{".", ".."} {
|
||||
if got := dirs[i].Name; got != want {
|
||||
return dirs, fmt.Errorf("wrong name, want: %s, got: %s", want, got)
|
||||
}
|
||||
if got := dirs[i].Type; got != linux.DT_DIR {
|
||||
return dirs, fmt.Errorf("wrong type, want: %d, got: %d", linux.DT_DIR, got)
|
||||
}
|
||||
}
|
||||
return dirs[2:], nil
|
||||
}
|
||||
|
||||
func checkTasksStaticFiles(gots []vfs.Dirent) ([]vfs.Dirent, error) {
|
||||
wants := map[string]vfs.Dirent{
|
||||
"loadavg": vfs.Dirent{Type: linux.DT_REG},
|
||||
"meminfo": vfs.Dirent{Type: linux.DT_REG},
|
||||
"mounts": vfs.Dirent{Type: linux.DT_LNK},
|
||||
"self": vfs.Dirent{Type: linux.DT_LNK},
|
||||
"stat": vfs.Dirent{Type: linux.DT_REG},
|
||||
"thread-self": vfs.Dirent{Type: linux.DT_LNK},
|
||||
"version": vfs.Dirent{Type: linux.DT_REG},
|
||||
}
|
||||
return checkFiles(gots, wants)
|
||||
}
|
||||
|
||||
func checkTaskStaticFiles(gots []vfs.Dirent) ([]vfs.Dirent, error) {
|
||||
wants := map[string]vfs.Dirent{
|
||||
"io": vfs.Dirent{Type: linux.DT_REG},
|
||||
"maps": vfs.Dirent{Type: linux.DT_REG},
|
||||
"smaps": vfs.Dirent{Type: linux.DT_REG},
|
||||
"stat": vfs.Dirent{Type: linux.DT_REG},
|
||||
"statm": vfs.Dirent{Type: linux.DT_REG},
|
||||
"status": vfs.Dirent{Type: linux.DT_REG},
|
||||
}
|
||||
return checkFiles(gots, wants)
|
||||
}
|
||||
|
||||
func checkFiles(gots []vfs.Dirent, wants map[string]vfs.Dirent) ([]vfs.Dirent, error) {
|
||||
// Go over all files, when there is a match, the file is removed from both
|
||||
// 'gots' and 'wants'. wants is expected to reach 0, as all files must
|
||||
// be present. Remaining files in 'gots', is returned to caller to decide
|
||||
// whether this is valid or not.
|
||||
for i := 0; i < len(gots); i++ {
|
||||
got := gots[i]
|
||||
want, ok := wants[got.Name]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if want.Type != got.Type {
|
||||
return gots, fmt.Errorf("wrong file type, want: %v, got: %v: %+v", want.Type, got.Type, got)
|
||||
}
|
||||
|
||||
delete(wants, got.Name)
|
||||
gots = append(gots[0:i], gots[i+1:]...)
|
||||
i--
|
||||
}
|
||||
if len(wants) != 0 {
|
||||
return gots, fmt.Errorf("not all files were found, missing: %+v", wants)
|
||||
}
|
||||
return gots, nil
|
||||
}
|
||||
|
||||
func setup() (context.Context, *vfs.VirtualFilesystem, vfs.VirtualDentry, error) {
|
||||
k, err := boot()
|
||||
if err != nil {
|
||||
return nil, nil, vfs.VirtualDentry{}, fmt.Errorf("creating kernel: %v", err)
|
||||
}
|
||||
|
||||
ctx := k.SupervisorContext()
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
vfsObj := vfs.New()
|
||||
vfsObj.MustRegisterFilesystemType("procfs", &procFSType{})
|
||||
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "procfs", &vfs.GetFilesystemOptions{})
|
||||
if err != nil {
|
||||
return nil, nil, vfs.VirtualDentry{}, fmt.Errorf("NewMountNamespace(): %v", err)
|
||||
}
|
||||
return ctx, vfsObj, mntns.Root(), nil
|
||||
}
|
||||
|
||||
func TestTasksEmpty(t *testing.T) {
|
||||
ctx, vfsObj, root, err := setup()
|
||||
if err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
defer root.DecRef()
|
||||
|
||||
fd, err := vfsObj.OpenAt(
|
||||
ctx,
|
||||
auth.CredentialsFromContext(ctx),
|
||||
&vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/")},
|
||||
&vfs.OpenOptions{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("vfsfs.OpenAt failed: %v", err)
|
||||
}
|
||||
|
||||
cb := testIterDirentsCallback{}
|
||||
if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
|
||||
t.Fatalf("IterDirents(): %v", err)
|
||||
}
|
||||
cb.dirents, err = checkDots(cb.dirents)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
cb.dirents, err = checkTasksStaticFiles(cb.dirents)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if len(cb.dirents) != 0 {
|
||||
t.Error("found more files than expected: %+v", cb.dirents)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTasks(t *testing.T) {
|
||||
ctx, vfsObj, root, err := setup()
|
||||
if err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
defer root.DecRef()
|
||||
|
||||
k := kernel.KernelFromContext(ctx)
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTask(): %v", err)
|
||||
}
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
|
||||
fd, err := vfsObj.OpenAt(
|
||||
ctx,
|
||||
auth.CredentialsFromContext(ctx),
|
||||
&vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/")},
|
||||
&vfs.OpenOptions{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("vfsfs.OpenAt(/) failed: %v", err)
|
||||
}
|
||||
|
||||
cb := testIterDirentsCallback{}
|
||||
if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
|
||||
t.Fatalf("IterDirents(): %v", err)
|
||||
}
|
||||
cb.dirents, err = checkDots(cb.dirents)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
cb.dirents, err = checkTasksStaticFiles(cb.dirents)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
lastPid := 0
|
||||
for _, d := range cb.dirents {
|
||||
pid, err := strconv.Atoi(d.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Invalid process directory %q", d.Name)
|
||||
}
|
||||
if lastPid > pid {
|
||||
t.Errorf("pids not in order: %v", cb.dirents)
|
||||
}
|
||||
found := false
|
||||
for _, t := range tasks {
|
||||
if k.TaskSet().Root.IDOfTask(t) == kernel.ThreadID(pid) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Additional task ID %d listed: %v", pid, tasks)
|
||||
}
|
||||
}
|
||||
|
||||
// Test lookup.
|
||||
for _, path := range []string{"/1", "/2"} {
|
||||
fd, err := vfsObj.OpenAt(
|
||||
ctx,
|
||||
auth.CredentialsFromContext(ctx),
|
||||
&vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse(path)},
|
||||
&vfs.OpenOptions{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("vfsfs.OpenAt(%q) failed: %v", path, err)
|
||||
}
|
||||
buf := make([]byte, 1)
|
||||
bufIOSeq := usermem.BytesIOSequence(buf)
|
||||
if _, err := fd.Read(ctx, bufIOSeq, vfs.ReadOptions{}); err != syserror.EISDIR {
|
||||
t.Errorf("wrong error reading directory: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := vfsObj.OpenAt(
|
||||
ctx,
|
||||
auth.CredentialsFromContext(ctx),
|
||||
&vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/9999")},
|
||||
&vfs.OpenOptions{},
|
||||
); err != syserror.ENOENT {
|
||||
t.Fatalf("wrong error from vfsfs.OpenAt(/9999): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTask(t *testing.T) {
|
||||
ctx, vfsObj, root, err := setup()
|
||||
if err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
defer root.DecRef()
|
||||
|
||||
k := kernel.KernelFromContext(ctx)
|
||||
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
|
||||
_, err = createTask(ctx, "name", tc)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTask(): %v", err)
|
||||
}
|
||||
|
||||
fd, err := vfsObj.OpenAt(
|
||||
ctx,
|
||||
auth.CredentialsFromContext(ctx),
|
||||
&vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/1")},
|
||||
&vfs.OpenOptions{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("vfsfs.OpenAt(/1) failed: %v", err)
|
||||
}
|
||||
|
||||
cb := testIterDirentsCallback{}
|
||||
if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
|
||||
t.Fatalf("IterDirents(): %v", err)
|
||||
}
|
||||
cb.dirents, err = checkDots(cb.dirents)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
cb.dirents, err = checkTaskStaticFiles(cb.dirents)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if len(cb.dirents) != 0 {
|
||||
t.Errorf("found more files than expected: %+v", cb.dirents)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcSelf(t *testing.T) {
|
||||
ctx, vfsObj, root, err := setup()
|
||||
if err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
defer root.DecRef()
|
||||
|
||||
k := kernel.KernelFromContext(ctx)
|
||||
tc := k.NewThreadGroup(nil, k.RootPIDNamespace(), kernel.NewSignalHandlers(), linux.SIGCHLD, k.GlobalInit().Limits())
|
||||
task, err := createTask(ctx, "name", tc)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTask(): %v", err)
|
||||
}
|
||||
|
||||
fd, err := vfsObj.OpenAt(
|
||||
task,
|
||||
auth.CredentialsFromContext(ctx),
|
||||
&vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/self/"), FollowFinalSymlink: true},
|
||||
&vfs.OpenOptions{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("vfsfs.OpenAt(/self/) failed: %v", err)
|
||||
}
|
||||
|
||||
cb := testIterDirentsCallback{}
|
||||
if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
|
||||
t.Fatalf("IterDirents(): %v", err)
|
||||
}
|
||||
cb.dirents, err = checkDots(cb.dirents)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
cb.dirents, err = checkTaskStaticFiles(cb.dirents)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
if len(cb.dirents) != 0 {
|
||||
t.Errorf("found more files than expected: %+v", cb.dirents)
|
||||
}
|
||||
}
|
||||
|
||||
func iterateDir(ctx context.Context, t *testing.T, vfsObj *vfs.VirtualFilesystem, root vfs.VirtualDentry, fd *vfs.FileDescription) {
|
||||
t.Logf("Iterating: /proc%s", fd.MappedName(ctx))
|
||||
|
||||
cb := testIterDirentsCallback{}
|
||||
if err := fd.Impl().IterDirents(ctx, &cb); err != nil {
|
||||
t.Fatalf("IterDirents(): %v", err)
|
||||
}
|
||||
var err error
|
||||
cb.dirents, err = checkDots(cb.dirents)
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
for _, d := range cb.dirents {
|
||||
childPath := path.Join(fd.MappedName(ctx), d.Name)
|
||||
if d.Type == linux.DT_LNK {
|
||||
link, err := vfsObj.ReadlinkAt(
|
||||
ctx,
|
||||
auth.CredentialsFromContext(ctx),
|
||||
&vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse(childPath)},
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("vfsfs.ReadlinkAt(%v) failed: %v", childPath, err)
|
||||
} else {
|
||||
t.Logf("Skipping symlink: /proc%s => %s", childPath, link)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
t.Logf("Opening: /proc%s", childPath)
|
||||
child, err := vfsObj.OpenAt(
|
||||
ctx,
|
||||
auth.CredentialsFromContext(ctx),
|
||||
&vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse(childPath)},
|
||||
&vfs.OpenOptions{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("vfsfs.OpenAt(%v) failed: %v", childPath, err)
|
||||
continue
|
||||
}
|
||||
stat, err := child.Stat(ctx, vfs.StatOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Stat(%v) failed: %v", childPath, err)
|
||||
}
|
||||
if got := linux.FileMode(stat.Mode).DirentType(); got != d.Type {
|
||||
t.Errorf("wrong file mode, stat: %v, dirent: %v", got, d.Type)
|
||||
}
|
||||
if d.Type == linux.DT_DIR {
|
||||
// Found another dir, let's do it again!
|
||||
iterateDir(ctx, t, vfsObj, root, child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTree iterates all directories and stats every file.
|
||||
func TestTree(t *testing.T) {
|
||||
uberCtx, vfsObj, root, err := setup()
|
||||
if err != nil {
|
||||
t.Fatalf("Setup failed: %v", err)
|
||||
}
|
||||
defer root.DecRef()
|
||||
|
||||
k := kernel.KernelFromContext(uberCtx)
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTask(): %v", err)
|
||||
}
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
|
||||
ctx := tasks[0]
|
||||
fd, err := vfsObj.OpenAt(
|
||||
ctx,
|
||||
auth.CredentialsFromContext(uberCtx),
|
||||
&vfs.PathOperation{Root: root, Start: root, Path: fspath.Parse("/")},
|
||||
&vfs.OpenOptions{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("vfsfs.OpenAt(/) failed: %v", err)
|
||||
}
|
||||
iterateDir(ctx, t, vfsObj, root, fd)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user