Initial procfs implementation in VFSv2

Updates #1195

PiperOrigin-RevId: 287227722
This commit is contained in:
Fabricio Voznika
2019-12-26 14:45:35 -08:00
committed by gVisor bot
parent 5b9034cc18
commit 3c125eb219
25 changed files with 1463 additions and 282 deletions
+1
View File
@@ -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"],
+16 -4
View File
@@ -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
+4 -1
View File
@@ -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
}
+9
View File
@@ -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 {
+1 -1
View File
@@ -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)
+45
View File
@@ -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
}
+30 -3
View File
@@ -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",
],
)
+149
View File
@@ -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
}
+69
View File
@@ -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
}
-25
View File
@@ -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.
+5 -3
View File
@@ -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 {
+4 -2
View File
@@ -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 {
-16
View File
@@ -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
+4 -2
View File
@@ -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
View File
@@ -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}
}
+272
View File
@@ -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
}
+162
View File
@@ -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
}
+92
View File
@@ -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
}
+412
View File
@@ -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