Add remaining /proc/* and /proc/sys/* files

Except for one under /proc/sys/net/ipv4/tcp_sack.
/proc/pid/* is still incomplete.

Updates #1195

PiperOrigin-RevId: 290120438
This commit is contained in:
Fabricio Voznika
2020-01-16 12:30:21 -08:00
committed by gVisor bot
parent fea1ce655d
commit 7b7c31820b
17 changed files with 508 additions and 424 deletions
+21 -23
View File
@@ -657,30 +657,28 @@ func (fs *FeatureSet) FlagsString(cpuinfoOnly bool) string {
return strings.Join(s, " ")
}
// CPUInfo is to generate a section of one cpu in /proc/cpuinfo. This is a
// minimal /proc/cpuinfo, it is missing some fields like "microcode" that are
// WriteCPUInfoTo is to generate a section of one cpu in /proc/cpuinfo. This is
// a minimal /proc/cpuinfo, it is missing some fields like "microcode" that are
// not always printed in Linux. The bogomips field is simply made up.
func (fs FeatureSet) CPUInfo(cpu uint) string {
var b bytes.Buffer
fmt.Fprintf(&b, "processor\t: %d\n", cpu)
fmt.Fprintf(&b, "vendor_id\t: %s\n", fs.VendorID)
fmt.Fprintf(&b, "cpu family\t: %d\n", ((fs.ExtendedFamily<<4)&0xff)|fs.Family)
fmt.Fprintf(&b, "model\t\t: %d\n", ((fs.ExtendedModel<<4)&0xff)|fs.Model)
fmt.Fprintf(&b, "model name\t: %s\n", "unknown") // Unknown for now.
fmt.Fprintf(&b, "stepping\t: %s\n", "unknown") // Unknown for now.
fmt.Fprintf(&b, "cpu MHz\t\t: %.3f\n", cpuFreqMHz)
fmt.Fprintln(&b, "fpu\t\t: yes")
fmt.Fprintln(&b, "fpu_exception\t: yes")
fmt.Fprintf(&b, "cpuid level\t: %d\n", uint32(xSaveInfo)) // Same as ax in vendorID.
fmt.Fprintln(&b, "wp\t\t: yes")
fmt.Fprintf(&b, "flags\t\t: %s\n", fs.FlagsString(true))
fmt.Fprintf(&b, "bogomips\t: %.02f\n", cpuFreqMHz) // It's bogus anyway.
fmt.Fprintf(&b, "clflush size\t: %d\n", fs.CacheLine)
fmt.Fprintf(&b, "cache_alignment\t: %d\n", fs.CacheLine)
fmt.Fprintf(&b, "address sizes\t: %d bits physical, %d bits virtual\n", 46, 48)
fmt.Fprintln(&b, "power management:") // This is always here, but can be blank.
fmt.Fprintln(&b, "") // The /proc/cpuinfo file ends with an extra newline.
return b.String()
func (fs FeatureSet) WriteCPUInfoTo(cpu uint, b *bytes.Buffer) {
fmt.Fprintf(b, "processor\t: %d\n", cpu)
fmt.Fprintf(b, "vendor_id\t: %s\n", fs.VendorID)
fmt.Fprintf(b, "cpu family\t: %d\n", ((fs.ExtendedFamily<<4)&0xff)|fs.Family)
fmt.Fprintf(b, "model\t\t: %d\n", ((fs.ExtendedModel<<4)&0xff)|fs.Model)
fmt.Fprintf(b, "model name\t: %s\n", "unknown") // Unknown for now.
fmt.Fprintf(b, "stepping\t: %s\n", "unknown") // Unknown for now.
fmt.Fprintf(b, "cpu MHz\t\t: %.3f\n", cpuFreqMHz)
fmt.Fprintln(b, "fpu\t\t: yes")
fmt.Fprintln(b, "fpu_exception\t: yes")
fmt.Fprintf(b, "cpuid level\t: %d\n", uint32(xSaveInfo)) // Same as ax in vendorID.
fmt.Fprintln(b, "wp\t\t: yes")
fmt.Fprintf(b, "flags\t\t: %s\n", fs.FlagsString(true))
fmt.Fprintf(b, "bogomips\t: %.02f\n", cpuFreqMHz) // It's bogus anyway.
fmt.Fprintf(b, "clflush size\t: %d\n", fs.CacheLine)
fmt.Fprintf(b, "cache_alignment\t: %d\n", fs.CacheLine)
fmt.Fprintf(b, "address sizes\t: %d bits physical, %d bits virtual\n", 46, 48)
fmt.Fprintln(b, "power management:") // This is always here, but can be blank.
fmt.Fprintln(b, "") // The /proc/cpuinfo file ends with an extra newline.
}
const (
+5 -3
View File
@@ -15,6 +15,8 @@
package proc
import (
"bytes"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/fs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
@@ -27,9 +29,9 @@ func newCPUInfo(ctx context.Context, msrc *fs.MountSource) *fs.Inode {
// Kernel is always initialized with a FeatureSet.
panic("cpuinfo read with nil FeatureSet")
}
contents := make([]byte, 0, 1024)
var buf bytes.Buffer
for i, max := uint(0), k.ApplicationCores(); i < max; i++ {
contents = append(contents, []byte(features.CPUInfo(i))...)
features.WriteCPUInfoTo(i, &buf)
}
return newStaticProcInode(ctx, msrc, contents)
return newStaticProcInode(ctx, msrc, buf.Bytes())
}
@@ -510,3 +510,43 @@ type InodeSymlink struct {
func (InodeSymlink) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
return nil, syserror.ELOOP
}
// StaticDirectory is a standard implementation of a directory with static
// contents.
//
// +stateify savable
type StaticDirectory struct {
InodeNotSymlink
InodeDirectoryNoNewChildren
InodeAttrs
InodeNoDynamicLookup
OrderedChildren
}
var _ Inode = (*StaticDirectory)(nil)
// NewStaticDir creates a new static directory and returns its dentry.
func NewStaticDir(creds *auth.Credentials, ino uint64, perm linux.FileMode, children map[string]*Dentry) *Dentry {
if perm&^linux.PermissionsMask != 0 {
panic(fmt.Sprintf("Only permission mask must be set: %x", perm&linux.PermissionsMask))
}
inode := &StaticDirectory{}
inode.InodeAttrs.Init(creds, ino, linux.ModeDirectory|perm)
dentry := &Dentry{}
dentry.Init(inode)
inode.OrderedChildren.Init(OrderedChildrenOptions{})
links := inode.OrderedChildren.Populate(dentry, children)
inode.IncLinks(links)
return dentry
}
// Open implements kernfs.Inode.
func (s *StaticDirectory) Open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
fd := &GenericDirectoryFD{}
fd.Init(rp.Mount(), vfsd, &s.OrderedChildren, flags)
return fd.VFSFileDescription(), nil
}
+4 -7
View File
@@ -7,17 +7,13 @@ go_library(
name = "proc",
srcs = [
"filesystem.go",
"loadavg.go",
"meminfo.go",
"mounts.go",
"net.go",
"stat.go",
"sys.go",
"task.go",
"task_files.go",
"tasks.go",
"tasks_files.go",
"version.go",
"tasks_net.go",
"tasks_sys.go",
],
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/proc",
deps = [
@@ -30,6 +26,7 @@ go_library(
"//pkg/sentry/inet",
"//pkg/sentry/kernel",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/time",
"//pkg/sentry/limits",
"//pkg/sentry/mm",
"//pkg/sentry/socket",
@@ -47,7 +44,7 @@ go_test(
size = "small",
srcs = [
"boot_test.go",
"net_test.go",
"tasks_sys_test.go",
"tasks_test.go",
],
embed = [":proc"],
+11
View File
@@ -67,3 +67,14 @@ func newDentry(creds *auth.Credentials, ino uint64, perm linux.FileMode, inode d
d.Init(inode)
return d
}
type staticFile struct {
kernfs.DynamicBytesFile
vfs.StaticData
}
var _ dynamicInode = (*staticFile)(nil)
func newStaticFile(data string) *staticFile {
return &staticFile{StaticData: vfs.StaticData{Data: data}}
}
-42
View File
@@ -1,42 +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
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
)
// loadavgData backs /proc/loadavg.
//
// +stateify savable
type loadavgData struct {
kernfs.DynamicBytesFile
}
var _ dynamicInode = (*loadavgData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *loadavgData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/62345059): Include real data in fields.
// Column 1-3: CPU and IO utilization of the last 1, 5, and 10 minute periods.
// Column 4-5: currently running processes and the total number of processes.
// Column 6: the last process ID used.
fmt.Fprintf(buf, "%.2f %.2f %.2f %d/%d %d\n", 0.00, 0.00, 0.00, 0, 0, 0)
return nil
}
-79
View File
@@ -1,79 +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
import (
"bytes"
"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"
)
// meminfoData implements vfs.DynamicBytesSource for /proc/meminfo.
//
// +stateify savable
type meminfoData struct {
kernfs.DynamicBytesFile
// k is the owning Kernel.
k *kernel.Kernel
}
var _ dynamicInode = (*meminfoData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *meminfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
mf := d.k.MemoryFile()
mf.UpdateUsage()
snapshot, totalUsage := usage.MemoryAccounting.Copy()
totalSize := usage.TotalMemory(mf.TotalSize(), totalUsage)
anon := snapshot.Anonymous + snapshot.Tmpfs
file := snapshot.PageCache + snapshot.Mapped
// We don't actually have active/inactive LRUs, so just make up numbers.
activeFile := (file / 2) &^ (usermem.PageSize - 1)
inactiveFile := file - activeFile
fmt.Fprintf(buf, "MemTotal: %8d kB\n", totalSize/1024)
memFree := (totalSize - totalUsage) / 1024
// We use MemFree as MemAvailable because we don't swap.
// TODO(rahat): When reclaim is implemented the value of MemAvailable
// should change.
fmt.Fprintf(buf, "MemFree: %8d kB\n", memFree)
fmt.Fprintf(buf, "MemAvailable: %8d kB\n", memFree)
fmt.Fprintf(buf, "Buffers: 0 kB\n") // memory usage by block devices
fmt.Fprintf(buf, "Cached: %8d kB\n", (file+snapshot.Tmpfs)/1024)
// Emulate a system with no swap, which disables inactivation of anon pages.
fmt.Fprintf(buf, "SwapCache: 0 kB\n")
fmt.Fprintf(buf, "Active: %8d kB\n", (anon+activeFile)/1024)
fmt.Fprintf(buf, "Inactive: %8d kB\n", inactiveFile/1024)
fmt.Fprintf(buf, "Active(anon): %8d kB\n", anon/1024)
fmt.Fprintf(buf, "Inactive(anon): 0 kB\n")
fmt.Fprintf(buf, "Active(file): %8d kB\n", activeFile/1024)
fmt.Fprintf(buf, "Inactive(file): %8d kB\n", inactiveFile/1024)
fmt.Fprintf(buf, "Unevictable: 0 kB\n") // TODO(b/31823263)
fmt.Fprintf(buf, "Mlocked: 0 kB\n") // TODO(b/31823263)
fmt.Fprintf(buf, "SwapTotal: 0 kB\n")
fmt.Fprintf(buf, "SwapFree: 0 kB\n")
fmt.Fprintf(buf, "Dirty: 0 kB\n")
fmt.Fprintf(buf, "Writeback: 0 kB\n")
fmt.Fprintf(buf, "AnonPages: %8d kB\n", anon/1024)
fmt.Fprintf(buf, "Mapped: %8d kB\n", file/1024) // doesn't count mapped tmpfs, which we don't know
fmt.Fprintf(buf, "Shmem: %8d kB\n", snapshot.Tmpfs/1024)
return nil
}
-129
View File
@@ -1,129 +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
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"
)
// cpuStats contains the breakdown of CPU time for /proc/stat.
type cpuStats struct {
// user is time spent in userspace tasks with non-positive niceness.
user uint64
// nice is time spent in userspace tasks with positive niceness.
nice uint64
// system is time spent in non-interrupt kernel context.
system uint64
// idle is time spent idle.
idle uint64
// ioWait is time spent waiting for IO.
ioWait uint64
// irq is time spent in interrupt context.
irq uint64
// softirq is time spent in software interrupt context.
softirq uint64
// steal is involuntary wait time.
steal uint64
// guest is time spent in guests with non-positive niceness.
guest uint64
// guestNice is time spent in guests with positive niceness.
guestNice uint64
}
// String implements fmt.Stringer.
func (c cpuStats) String() string {
return fmt.Sprintf("%d %d %d %d %d %d %d %d %d %d", c.user, c.nice, c.system, c.idle, c.ioWait, c.irq, c.softirq, c.steal, c.guest, c.guestNice)
}
// statData implements vfs.DynamicBytesSource for /proc/stat.
//
// +stateify savable
type statData struct {
kernfs.DynamicBytesFile
// k is the owning Kernel.
k *kernel.Kernel
}
var _ dynamicInode = (*statData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (s *statData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/37226836): We currently export only zero CPU stats. We could
// at least provide some aggregate stats.
var cpu cpuStats
fmt.Fprintf(buf, "cpu %s\n", cpu)
for c, max := uint(0), s.k.ApplicationCores(); c < max; c++ {
fmt.Fprintf(buf, "cpu%d %s\n", c, cpu)
}
// The total number of interrupts is dependent on the CPUs and PCI
// devices on the system. See arch_probe_nr_irqs.
//
// Since we don't report real interrupt stats, just choose an arbitrary
// value from a representative VM.
const numInterrupts = 256
// The Kernel doesn't handle real interrupts, so report all zeroes.
// TODO(b/37226836): We could count page faults as #PF.
fmt.Fprintf(buf, "intr 0") // total
for i := 0; i < numInterrupts; i++ {
fmt.Fprintf(buf, " 0")
}
fmt.Fprintf(buf, "\n")
// Total number of context switches.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "ctxt 0\n")
// CLOCK_REALTIME timestamp from boot, in seconds.
fmt.Fprintf(buf, "btime %d\n", s.k.Timekeeper().BootTime().Seconds())
// Total number of clones.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "processes 0\n")
// Number of runnable tasks.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "procs_running 0\n")
// Number of tasks waiting on IO.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "procs_blocked 0\n")
// Number of each softirq handled.
fmt.Fprintf(buf, "softirq 0") // total
for i := 0; i < linux.NumSoftIRQ; i++ {
fmt.Fprintf(buf, " 0")
}
fmt.Fprintf(buf, "\n")
return nil
}
-51
View File
@@ -1,51 +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
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
// mmapMinAddrData implements vfs.DynamicBytesSource for
// /proc/sys/vm/mmap_min_addr.
//
// +stateify savable
type mmapMinAddrData struct {
k *kernel.Kernel
}
var _ vfs.DynamicBytesSource = (*mmapMinAddrData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *mmapMinAddrData) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "%d\n", d.k.Platform.MinUserAddress())
return nil
}
// +stateify savable
type overcommitMemory struct{}
var _ vfs.DynamicBytesSource = (*overcommitMemory)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *overcommitMemory) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "0\n")
return nil
}
+6 -6
View File
@@ -50,15 +50,15 @@ func newTaskInode(inoGen InoGenerator, task *kernel.Task, pidns *kernel.PIDNames
//"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}),
"io": newTaskOwnedFile(task, inoGen.NextIno(), 0400, newIO(task, isThreadGroup)),
"maps": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &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}),
"smaps": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &smapsData{task: task}),
"stat": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &taskStatData{t: task, pidns: pidns, tgstats: isThreadGroup}),
"statm": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &statmData{t: task}),
"status": newTaskOwnedFile(task, inoGen.NextIno(), 0444, &statusData{t: task, pidns: pidns}),
//"uid_map": newUIDMap(t, msrc),
}
if isThreadGroup {
+29 -12
View File
@@ -15,6 +15,7 @@
package proc
import (
"bytes"
"sort"
"strconv"
@@ -28,9 +29,8 @@ import (
)
const (
defaultPermission = 0444
selfName = "self"
threadSelfName = "thread-self"
selfName = "self"
threadSelfName = "thread-self"
)
// InoGenerator generates unique inode numbers for a given filesystem.
@@ -61,15 +61,15 @@ 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"),
"stat": newDentry(root, inoGen.NextIno(), defaultPermission, &statData{k: k}),
//"uptime": newUptime(ctx, msrc),
//"version": newVersionData(root, inoGen.NextIno(), k),
"version": newDentry(root, inoGen.NextIno(), defaultPermission, &versionData{k: k}),
"cpuinfo": newDentry(root, inoGen.NextIno(), 0444, newStaticFile(cpuInfoData(k))),
//"filesystems": newDentry(root, inoGen.NextIno(), 0444, &filesystemsData{}),
"loadavg": newDentry(root, inoGen.NextIno(), 0444, &loadavgData{}),
"sys": newSysDir(root, inoGen),
"meminfo": newDentry(root, inoGen.NextIno(), 0444, &meminfoData{}),
"mounts": kernfs.NewStaticSymlink(root, inoGen.NextIno(), 0777, "self/mounts"),
"stat": newDentry(root, inoGen.NextIno(), 0444, &statData{}),
"uptime": newDentry(root, inoGen.NextIno(), 0444, &uptimeData{}),
"version": newDentry(root, inoGen.NextIno(), 0444, &versionData{}),
}
inode := &tasksInode{
@@ -216,3 +216,20 @@ func (i *tasksInode) Stat(vsfs *vfs.Filesystem) linux.Statx {
return stat
}
func cpuInfoData(k *kernel.Kernel) string {
features := k.FeatureSet()
if features == nil {
// Kernel is always initialized with a FeatureSet.
panic("cpuinfo read with nil FeatureSet")
}
var buf bytes.Buffer
for i, max := uint(0), k.ApplicationCores(); i < max; i++ {
features.WriteCPUInfoTo(i, &buf)
}
return buf.String()
}
func shmData(v uint64) dynamicInode {
return newStaticFile(strconv.FormatUint(v, 10))
}
+245
View File
@@ -15,6 +15,7 @@
package proc
import (
"bytes"
"fmt"
"strconv"
@@ -23,6 +24,9 @@ import (
"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/kernel/time"
"gvisor.dev/gvisor/pkg/sentry/usage"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/syserror"
)
@@ -90,3 +94,244 @@ func (s *threadSelfSymlink) Readlink(ctx context.Context) (string, error) {
}
return fmt.Sprintf("%d/task/%d", tgid, tid), nil
}
// cpuStats contains the breakdown of CPU time for /proc/stat.
type cpuStats struct {
// user is time spent in userspace tasks with non-positive niceness.
user uint64
// nice is time spent in userspace tasks with positive niceness.
nice uint64
// system is time spent in non-interrupt kernel context.
system uint64
// idle is time spent idle.
idle uint64
// ioWait is time spent waiting for IO.
ioWait uint64
// irq is time spent in interrupt context.
irq uint64
// softirq is time spent in software interrupt context.
softirq uint64
// steal is involuntary wait time.
steal uint64
// guest is time spent in guests with non-positive niceness.
guest uint64
// guestNice is time spent in guests with positive niceness.
guestNice uint64
}
// String implements fmt.Stringer.
func (c cpuStats) String() string {
return fmt.Sprintf("%d %d %d %d %d %d %d %d %d %d", c.user, c.nice, c.system, c.idle, c.ioWait, c.irq, c.softirq, c.steal, c.guest, c.guestNice)
}
// statData implements vfs.DynamicBytesSource for /proc/stat.
//
// +stateify savable
type statData struct {
kernfs.DynamicBytesFile
// k is the owning Kernel.
k *kernel.Kernel
}
var _ dynamicInode = (*statData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (s *statData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/37226836): We currently export only zero CPU stats. We could
// at least provide some aggregate stats.
var cpu cpuStats
fmt.Fprintf(buf, "cpu %s\n", cpu)
for c, max := uint(0), s.k.ApplicationCores(); c < max; c++ {
fmt.Fprintf(buf, "cpu%d %s\n", c, cpu)
}
// The total number of interrupts is dependent on the CPUs and PCI
// devices on the system. See arch_probe_nr_irqs.
//
// Since we don't report real interrupt stats, just choose an arbitrary
// value from a representative VM.
const numInterrupts = 256
// The Kernel doesn't handle real interrupts, so report all zeroes.
// TODO(b/37226836): We could count page faults as #PF.
fmt.Fprintf(buf, "intr 0") // total
for i := 0; i < numInterrupts; i++ {
fmt.Fprintf(buf, " 0")
}
fmt.Fprintf(buf, "\n")
// Total number of context switches.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "ctxt 0\n")
// CLOCK_REALTIME timestamp from boot, in seconds.
fmt.Fprintf(buf, "btime %d\n", s.k.Timekeeper().BootTime().Seconds())
// Total number of clones.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "processes 0\n")
// Number of runnable tasks.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "procs_running 0\n")
// Number of tasks waiting on IO.
// TODO(b/37226836): Count this.
fmt.Fprintf(buf, "procs_blocked 0\n")
// Number of each softirq handled.
fmt.Fprintf(buf, "softirq 0") // total
for i := 0; i < linux.NumSoftIRQ; i++ {
fmt.Fprintf(buf, " 0")
}
fmt.Fprintf(buf, "\n")
return nil
}
// loadavgData backs /proc/loadavg.
//
// +stateify savable
type loadavgData struct {
kernfs.DynamicBytesFile
}
var _ dynamicInode = (*loadavgData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *loadavgData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// TODO(b/62345059): Include real data in fields.
// Column 1-3: CPU and IO utilization of the last 1, 5, and 10 minute periods.
// Column 4-5: currently running processes and the total number of processes.
// Column 6: the last process ID used.
fmt.Fprintf(buf, "%.2f %.2f %.2f %d/%d %d\n", 0.00, 0.00, 0.00, 0, 0, 0)
return nil
}
// meminfoData implements vfs.DynamicBytesSource for /proc/meminfo.
//
// +stateify savable
type meminfoData struct {
kernfs.DynamicBytesFile
// k is the owning Kernel.
k *kernel.Kernel
}
var _ dynamicInode = (*meminfoData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *meminfoData) Generate(ctx context.Context, buf *bytes.Buffer) error {
mf := d.k.MemoryFile()
mf.UpdateUsage()
snapshot, totalUsage := usage.MemoryAccounting.Copy()
totalSize := usage.TotalMemory(mf.TotalSize(), totalUsage)
anon := snapshot.Anonymous + snapshot.Tmpfs
file := snapshot.PageCache + snapshot.Mapped
// We don't actually have active/inactive LRUs, so just make up numbers.
activeFile := (file / 2) &^ (usermem.PageSize - 1)
inactiveFile := file - activeFile
fmt.Fprintf(buf, "MemTotal: %8d kB\n", totalSize/1024)
memFree := (totalSize - totalUsage) / 1024
// We use MemFree as MemAvailable because we don't swap.
// TODO(rahat): When reclaim is implemented the value of MemAvailable
// should change.
fmt.Fprintf(buf, "MemFree: %8d kB\n", memFree)
fmt.Fprintf(buf, "MemAvailable: %8d kB\n", memFree)
fmt.Fprintf(buf, "Buffers: 0 kB\n") // memory usage by block devices
fmt.Fprintf(buf, "Cached: %8d kB\n", (file+snapshot.Tmpfs)/1024)
// Emulate a system with no swap, which disables inactivation of anon pages.
fmt.Fprintf(buf, "SwapCache: 0 kB\n")
fmt.Fprintf(buf, "Active: %8d kB\n", (anon+activeFile)/1024)
fmt.Fprintf(buf, "Inactive: %8d kB\n", inactiveFile/1024)
fmt.Fprintf(buf, "Active(anon): %8d kB\n", anon/1024)
fmt.Fprintf(buf, "Inactive(anon): 0 kB\n")
fmt.Fprintf(buf, "Active(file): %8d kB\n", activeFile/1024)
fmt.Fprintf(buf, "Inactive(file): %8d kB\n", inactiveFile/1024)
fmt.Fprintf(buf, "Unevictable: 0 kB\n") // TODO(b/31823263)
fmt.Fprintf(buf, "Mlocked: 0 kB\n") // TODO(b/31823263)
fmt.Fprintf(buf, "SwapTotal: 0 kB\n")
fmt.Fprintf(buf, "SwapFree: 0 kB\n")
fmt.Fprintf(buf, "Dirty: 0 kB\n")
fmt.Fprintf(buf, "Writeback: 0 kB\n")
fmt.Fprintf(buf, "AnonPages: %8d kB\n", anon/1024)
fmt.Fprintf(buf, "Mapped: %8d kB\n", file/1024) // doesn't count mapped tmpfs, which we don't know
fmt.Fprintf(buf, "Shmem: %8d kB\n", snapshot.Tmpfs/1024)
return nil
}
// uptimeData implements vfs.DynamicBytesSource for /proc/uptime.
//
// +stateify savable
type uptimeData struct {
kernfs.DynamicBytesFile
}
var _ dynamicInode = (*uptimeData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (*uptimeData) Generate(ctx context.Context, buf *bytes.Buffer) error {
k := kernel.KernelFromContext(ctx)
now := time.NowFromContext(ctx)
// Pretend that we've spent zero time sleeping (second number).
fmt.Fprintf(buf, "%.2f 0.00\n", now.Sub(k.Timekeeper().BootTime()).Seconds())
return nil
}
// versionData implements vfs.DynamicBytesSource for /proc/version.
//
// +stateify savable
type versionData struct {
kernfs.DynamicBytesFile
// k is the owning Kernel.
k *kernel.Kernel
}
var _ dynamicInode = (*versionData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (v *versionData) Generate(ctx context.Context, buf *bytes.Buffer) error {
init := v.k.GlobalInit()
if init == nil {
// Attempted to read before the init Task is created. This can
// only occur during startup, which should never need to read
// this file.
panic("Attempted to read version before initial Task is available")
}
// /proc/version takes the form:
//
// "SYSNAME version RELEASE (COMPILE_USER@COMPILE_HOST)
// (COMPILER_VERSION) VERSION"
//
// where:
// - SYSNAME, RELEASE, and VERSION are the same as returned by
// sys_utsname
// - COMPILE_USER is the user that build the kernel
// - COMPILE_HOST is the hostname of the machine on which the kernel
// was built
// - COMPILER_VERSION is the version reported by the building compiler
//
// Since we don't really want to expose build information to
// applications, those fields are omitted.
//
// FIXME(mpratt): Using Version from the init task SyscallTable
// disregards the different version a task may have (e.g., in a uts
// namespace).
ver := init.Leader().SyscallTable().Version
fmt.Fprintf(buf, "%s version %s %s\n", ver.Sysname, ver.Release, ver.Version)
return nil
}
@@ -46,8 +46,7 @@ func (n *ifinet6) contents() []string {
for id, naddrs := range n.s.InterfaceAddrs() {
nic, ok := nics[id]
if !ok {
// NIC was added after NICNames was called. We'll just
// ignore it.
// NIC was added after NICNames was called. We'll just ignore it.
continue
}
+143
View File
@@ -0,0 +1,143 @@
// 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/kernel/auth"
)
// newSysDir returns the dentry corresponding to /proc/sys directory.
func newSysDir(root *auth.Credentials, inoGen InoGenerator) *kernfs.Dentry {
return kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
"kernel": kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
"hostname": newDentry(root, inoGen.NextIno(), 0444, &hostnameData{}),
"shmall": newDentry(root, inoGen.NextIno(), 0444, shmData(linux.SHMALL)),
"shmmax": newDentry(root, inoGen.NextIno(), 0444, shmData(linux.SHMMAX)),
"shmmni": newDentry(root, inoGen.NextIno(), 0444, shmData(linux.SHMMNI)),
}),
"vm": kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
"mmap_min_addr": newDentry(root, inoGen.NextIno(), 0444, &mmapMinAddrData{}),
"overcommit_memory": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0\n")),
}),
"net": newSysNetDir(root, inoGen),
})
}
// newSysNetDir returns the dentry corresponding to /proc/sys/net directory.
func newSysNetDir(root *auth.Credentials, inoGen InoGenerator) *kernfs.Dentry {
return kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
"net": kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
"ipv4": kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
// Add tcp_sack.
// TODO(gvisor.dev/issue/1195): tcp_sack allows write(2)
// "tcp_sack": newTCPSackInode(ctx, msrc, s),
// The following files are simple stubs until they are implemented in
// netstack, most of these files are configuration related. We use the
// value closest to the actual netstack behavior or any empty file, all
// of these files will have mode 0444 (read-only for all users).
"ip_local_port_range": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("16000 65535")),
"ip_local_reserved_ports": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("")),
"ipfrag_time": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("30")),
"ip_nonlocal_bind": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"ip_no_pmtu_disc": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("1")),
// tcp_allowed_congestion_control tell the user what they are able to
// do as an unprivledged process so we leave it empty.
"tcp_allowed_congestion_control": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("")),
"tcp_available_congestion_control": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("reno")),
"tcp_congestion_control": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("reno")),
// Many of the following stub files are features netstack doesn't
// support. The unsupported features return "0" to indicate they are
// disabled.
"tcp_base_mss": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("1280")),
"tcp_dsack": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_early_retrans": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_fack": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_fastopen": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_fastopen_key": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("")),
"tcp_invalid_ratelimit": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_keepalive_intvl": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_keepalive_probes": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_keepalive_time": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("7200")),
"tcp_mtu_probing": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_no_metrics_save": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("1")),
"tcp_probe_interval": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_probe_threshold": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"tcp_retries1": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("3")),
"tcp_retries2": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("15")),
"tcp_rfc1337": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("1")),
"tcp_slow_start_after_idle": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("1")),
"tcp_synack_retries": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("5")),
"tcp_syn_retries": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("3")),
"tcp_timestamps": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("1")),
}),
"core": kernfs.NewStaticDir(root, inoGen.NextIno(), 0555, map[string]*kernfs.Dentry{
"default_qdisc": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("pfifo_fast")),
"message_burst": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("10")),
"message_cost": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("5")),
"optmem_max": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("0")),
"rmem_default": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("212992")),
"rmem_max": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("212992")),
"somaxconn": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("128")),
"wmem_default": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("212992")),
"wmem_max": newDentry(root, inoGen.NextIno(), 0444, newStaticFile("212992")),
}),
}),
})
}
// mmapMinAddrData implements vfs.DynamicBytesSource for
// /proc/sys/vm/mmap_min_addr.
//
// +stateify savable
type mmapMinAddrData struct {
kernfs.DynamicBytesFile
k *kernel.Kernel
}
var _ dynamicInode = (*mmapMinAddrData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (d *mmapMinAddrData) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "%d\n", d.k.Platform.MinUserAddress())
return nil
}
// hostnameData implements vfs.DynamicBytesSource for /proc/sys/kernel/hostname.
//
// +stateify savable
type hostnameData struct {
kernfs.DynamicBytesFile
}
var _ dynamicInode = (*hostnameData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (*hostnameData) Generate(ctx context.Context, buf *bytes.Buffer) error {
utsns := kernel.UTSNamespaceFromContext(ctx)
buf.WriteString(utsns.HostName())
buf.WriteString("\n")
return nil
}
+3
View File
@@ -69,12 +69,15 @@ func checkDots(dirs []vfs.Dirent) ([]vfs.Dirent, error) {
func checkTasksStaticFiles(gots []vfs.Dirent) ([]vfs.Dirent, error) {
wants := map[string]vfs.Dirent{
"cpuinfo": {Type: linux.DT_REG},
"loadavg": {Type: linux.DT_REG},
"meminfo": {Type: linux.DT_REG},
"mounts": {Type: linux.DT_LNK},
"self": selfLink,
"stat": {Type: linux.DT_REG},
"sys": {Type: linux.DT_DIR},
"thread-self": threadSelfLink,
"uptime": {Type: linux.DT_REG},
"version": {Type: linux.DT_REG},
}
return checkFiles(gots, wants)
-70
View File
@@ -1,70 +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
import (
"bytes"
"fmt"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
)
// versionData implements vfs.DynamicBytesSource for /proc/version.
//
// +stateify savable
type versionData struct {
kernfs.DynamicBytesFile
// k is the owning Kernel.
k *kernel.Kernel
}
var _ dynamicInode = (*versionData)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (v *versionData) Generate(ctx context.Context, buf *bytes.Buffer) error {
init := v.k.GlobalInit()
if init == nil {
// Attempted to read before the init Task is created. This can
// only occur during startup, which should never need to read
// this file.
panic("Attempted to read version before initial Task is available")
}
// /proc/version takes the form:
//
// "SYSNAME version RELEASE (COMPILE_USER@COMPILE_HOST)
// (COMPILER_VERSION) VERSION"
//
// where:
// - SYSNAME, RELEASE, and VERSION are the same as returned by
// sys_utsname
// - COMPILE_USER is the user that build the kernel
// - COMPILE_HOST is the hostname of the machine on which the kernel
// was built
// - COMPILER_VERSION is the version reported by the building compiler
//
// Since we don't really want to expose build information to
// applications, those fields are omitted.
//
// FIXME(mpratt): Using Version from the init task SyscallTable
// disregards the different version a task may have (e.g., in a uts
// namespace).
ver := init.Leader().SyscallTable().Version
fmt.Fprintf(buf, "%s version %s %s\n", ver.Sysname, ver.Release, ver.Version)
return nil
}