Implement sentry control commands for cgroupfs.

Add sentry control commands to read and write cgroup control
values.

PiperOrigin-RevId: 474663678
This commit is contained in:
Rahat Mahmood
2022-09-15 14:55:45 -07:00
committed by gVisor bot
parent a13fbe75e4
commit fc0e4d0a03
19 changed files with 680 additions and 42 deletions
+1
View File
@@ -11,6 +11,7 @@ proto_library(
go_library(
name = "control",
srcs = [
"cgroups.go",
"control.go",
"events.go",
"fs.go",
+159
View File
@@ -0,0 +1,159 @@
// Copyright 2022 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 control
import (
"fmt"
"strings"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/sentry/kernel"
)
// Cgroups contains the state for cgroupfs related control commands.
type Cgroups struct {
Kernel *kernel.Kernel
}
func (c *Cgroups) findCgroup(ctx context.Context, file CgroupControlFile) (kernel.Cgroup, error) {
ctl, err := file.controller()
if err != nil {
return kernel.Cgroup{}, err
}
return c.Kernel.CgroupRegistry().FindCgroup(ctx, ctl, file.Path)
}
// CgroupControlFile identifies a specific control file within a
// specific cgroup, for the hierarchy with a given controller.
type CgroupControlFile struct {
Controller string `json:"controller"`
Path string `json:"path"`
Name string `json:"name"`
}
func (f *CgroupControlFile) controller() (kernel.CgroupControllerType, error) {
return kernel.ParseCgroupController(f.Controller)
}
// CgroupsResult represents the result of a cgroup operation.
type CgroupsResult struct {
Data string `json:"value"`
IsError bool `json:"is_error"`
}
// AsError interprets the result as an error.
func (r *CgroupsResult) AsError() error {
if r.IsError {
return fmt.Errorf(r.Data)
}
return nil
}
// Unpack splits CgroupsResult into a (value, error) tuple.
func (r *CgroupsResult) Unpack() (string, error) {
if r.IsError {
return "", fmt.Errorf(r.Data)
}
return r.Data, nil
}
func newValue(val string) CgroupsResult {
return CgroupsResult{
Data: strings.TrimSpace(val),
}
}
func newError(err error) CgroupsResult {
return CgroupsResult{
Data: err.Error(),
IsError: true,
}
}
// CgroupsResults represents the list of results for a batch command.
type CgroupsResults struct {
Results []CgroupsResult `json:"results"`
}
func (o *CgroupsResults) appendValue(val string) {
o.Results = append(o.Results, newValue(val))
}
func (o *CgroupsResults) appendError(err error) {
o.Results = append(o.Results, newError(err))
}
// CgroupsReadArg represents the arguments for a single read command.
type CgroupsReadArg struct {
File CgroupControlFile `json:"file"`
}
// CgroupsReadArgs represents the list of arguments for a batched read command.
type CgroupsReadArgs struct {
Args []CgroupsReadArg `json:"args"`
}
// ReadControlFiles is an RPC stub for batch-reading cgroupfs control files.
func (c *Cgroups) ReadControlFiles(args *CgroupsReadArgs, out *CgroupsResults) error {
ctx := c.Kernel.SupervisorContext()
for _, arg := range args.Args {
cg, err := c.findCgroup(ctx, arg.File)
if err != nil {
out.appendError(err)
continue
}
val, err := cg.ReadControl(ctx, arg.File.Name)
if err != nil {
out.appendError(err)
} else {
out.appendValue(val)
}
}
return nil
}
// CgroupsWriteArg represents the arguments for a single write command.
type CgroupsWriteArg struct {
File CgroupControlFile `json:"file"`
Value string `json:"value"`
}
// CgroupsWriteArgs represents the lust of arguments for a batched write command.
type CgroupsWriteArgs struct {
Args []CgroupsWriteArg `json:"args"`
}
// WriteControlFiles is an RPC stub for batch-writing cgroupfs control files.
func (c *Cgroups) WriteControlFiles(args *CgroupsWriteArgs, out *CgroupsResults) error {
ctx := c.Kernel.SupervisorContext()
for _, arg := range args.Args {
cg, err := c.findCgroup(ctx, arg.File)
if err != nil {
out.appendError(err)
continue
}
err = cg.WriteControl(ctx, arg.File.Name, arg.Value)
if err != nil {
out.appendError(err)
} else {
out.appendValue("")
}
}
return nil
}
+1
View File
@@ -32,6 +32,7 @@ message ControlConfig {
PROC = 7;
STATE = 8;
DEBUG = 9;
CGROUPS = 10;
}
// allowed_controls represents which endpoints may be registered to the
+77 -8
View File
@@ -24,6 +24,7 @@ import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/kernel"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
@@ -84,9 +85,9 @@ func (c *controllerCommon) Enabled() bool {
return true
}
// RootCgroup implements kernel.CgroupController.RootCgroup.
func (c *controllerCommon) RootCgroup() kernel.Cgroup {
return c.fs.rootCgroup()
// EffectiveRootCgroup implements kernel.CgroupController.EffectiveRootCgroup.
func (c *controllerCommon) EffectiveRootCgroup() kernel.Cgroup {
return c.fs.effectiveRootCgroup()
}
// controller is an interface for common functionality related to all cgroups.
@@ -175,8 +176,8 @@ func (fs *filesystem) newCgroupInode(ctx context.Context, creds *auth.Credential
c.dir.cgi = c
contents := make(map[string]kernfs.Inode)
contents["cgroup.procs"] = fs.newControllerWritableFile(ctx, creds, &cgroupProcsData{c})
contents["tasks"] = fs.newControllerWritableFile(ctx, creds, &tasksData{c})
contents["cgroup.procs"] = fs.newControllerWritableFile(ctx, creds, &cgroupProcsData{c}, false)
contents["tasks"] = fs.newControllerWritableFile(ctx, creds, &tasksData{c}, false)
if parent != nil {
for ty, ctl := range parent.controllers {
@@ -323,6 +324,64 @@ func (c *cgroupInode) Charge(t *kernel.Task, d *kernfs.Dentry, ctlType kernel.Cg
return nil
}
// ReadControl implements kernel.CgroupImpl.ReadControl.
func (c *cgroupInode) ReadControl(ctx context.Context, name string) (string, error) {
c.fs.tasksMu.RLock()
defer c.fs.tasksMu.RUnlock()
cfi, err := c.Lookup(ctx, name)
if err != nil {
return "", fmt.Errorf("no such control file")
}
cbf, ok := cfi.(controllerFileImpl)
if !ok {
return "", fmt.Errorf("no such control file")
}
if !cbf.AllowBackgroundAccess() {
return "", fmt.Errorf("this control may not be accessed from a background context")
}
var buf bytes.Buffer
err = cbf.Source().Data().Generate(ctx, &buf)
return buf.String(), err
}
// WriteControl implements kernel.CgroupImpl.WriteControl.
func (c *cgroupInode) WriteControl(ctx context.Context, name string, value string) error {
c.fs.tasksMu.RLock()
defer c.fs.tasksMu.RUnlock()
cfi, err := c.Lookup(ctx, name)
if err != nil {
return fmt.Errorf("no such control file")
}
// Do the more general cast first so we can give a meaningful error message when
// the control file exists, but isn't accessible (either due to being
// unwritable, or not being available from a background context).
cbf, ok := cfi.(controllerFileImpl)
if !ok {
return fmt.Errorf("no such control file")
}
if !cbf.AllowBackgroundAccess() {
return fmt.Errorf("this control may not be accessed from a background context")
}
wcbf, ok := cfi.(writableControllerFileImpl)
if !ok {
return fmt.Errorf("control file not writable")
}
ioSeq := usermem.BytesIOSequence([]byte(value))
n, err := wcbf.WriteBackground(ctx, ioSeq)
if err != nil {
return err
}
if n != int64(len(value)) {
return fmt.Errorf("short write")
}
return nil
}
func sortTIDs(tids []kernel.ThreadID) {
sort.Slice(tids, func(i, j int) bool { return tids[i] < tids[j] })
}
@@ -434,9 +493,7 @@ func (d *tasksData) Write(ctx context.Context, fd *vfs.FileDescription, src user
func parseInt64FromString(ctx context.Context, src usermem.IOSequence) (val, len int64, err error) {
const maxInt64StrLen = 20 // i.e. len(fmt.Sprintf("%d", math.MinInt64)) == 20
t := kernel.TaskFromContext(ctx)
buf := t.CopyScratchBuffer(maxInt64StrLen)
buf := copyScratchBufferFromContext(ctx, maxInt64StrLen)
n, err := src.CopyIn(ctx, buf)
if err != nil {
return 0, int64(n), err
@@ -454,6 +511,18 @@ func parseInt64FromString(ctx context.Context, src usermem.IOSequence) (val, len
return val, int64(n), nil
}
// copyScratchBufferFromContext returns a scratch buffer of the given size. It
// tries to use the task's copy scratch buffer if we're on a task context,
// otherwise it allocates a new buffer.
func copyScratchBufferFromContext(ctx context.Context, size int) []byte {
t := kernel.TaskFromContext(ctx)
if t != nil {
return t.CopyScratchBuffer(hostarch.PageSize)
}
// Not on task context.
return make([]byte, hostarch.PageSize)
}
// controllerStateless partially implements controller. It stubs the migration
// methods with noops for a stateless controller.
type controllerStateless struct{}
+87 -7
View File
@@ -180,6 +180,14 @@ func (fs *filesystem) InitializeHierarchyID(hid uint32) {
fs.hierarchyID = hid
}
// RootCgroup implements kernel.cgroupFS.RootCgroup.
func (fs *filesystem) RootCgroup() kernel.Cgroup {
return kernel.Cgroup{
Dentry: fs.root,
CgroupImpl: fs.root.Inode().(kernel.CgroupImpl),
}
}
// Name implements vfs.FilesystemType.Name.
func (FilesystemType) Name() string {
return Name
@@ -383,7 +391,7 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
}
// Move all existing tasks to the root of the new hierarchy.
k.PopulateNewCgroupHierarchy(fs.rootCgroup())
k.PopulateNewCgroupHierarchy(fs.effectiveRootCgroup())
return fs.VFSFilesystem(), rootD.VFSDentry(), nil
}
@@ -441,7 +449,7 @@ func (fs *filesystem) prepareInitialCgroup(ctx context.Context, vfsObj *vfs.Virt
return nil
}
func (fs *filesystem) rootCgroup() kernel.Cgroup {
func (fs *filesystem) effectiveRootCgroup() kernel.Cgroup {
return kernel.Cgroup{
Dentry: fs.effectiveRoot,
CgroupImpl: fs.effectiveRoot.Inode().(kernel.CgroupImpl),
@@ -633,12 +641,58 @@ func (d *dir) forEachChildDir(fn func(*dir)) {
})
}
// controllerFileImpl represents common cgroupfs-specific operations for control
// files.
type controllerFileImpl interface {
// Source extracts the underlying DynamicBytesFile for a control file.
Source() *kernfs.DynamicBytesFile
// AllowBackgroundAccess indicates whether a control file can be accessed
// from a background (i.e. non-task) context. Some control files cannot be
// meaningfully accessed from a non-task context because accessing them
// either have side effects on the calling context (ex: task migration
// across cgroups), or they refer to data which must be interpreted within
// the calling context (ex: when referring to a pid, in which pid
// namespace?).
//
// Currently, all writable control files that allow access from a background
// process can handle a nil FD, since a background write doesn't explicitly
// open the control file. This is enforced through the
// writableControllerFileImpl.
AllowBackgroundAccess() bool
}
// writableControllerFileImpl represents common cgroupfs-specific operations for
// a writable control file.
type writableControllerFileImpl interface {
controllerFileImpl
// WriteBackground writes data to a control file from a background
// context. This means the write isn't performed through and FD may be
// performed from a background context.
//
// Control files that support this should also return true for
// controllerFileImpl.AllowBackgroundAccess().
WriteBackground(ctx context.Context, src usermem.IOSequence) (int64, error)
}
// controllerFile represents a generic control file that appears within a cgroup
// directory.
//
// +stateify savable
type controllerFile struct {
kernfs.DynamicBytesFile
allowBackgroundAccess bool
}
var _ controllerFileImpl = (*controllerFile)(nil)
// Source implements controllerFileImpl.Source.
func (f *controllerFile) Source() *kernfs.DynamicBytesFile {
return &f.DynamicBytesFile
}
// AllowBackgroundAccess implements controllerFileImpl.AllowBackgroundAccess.
func (f *controllerFile) AllowBackgroundAccess() bool {
return f.allowBackgroundAccess
}
// SetStat implements kernfs.Inode.SetStat.
@@ -646,14 +700,18 @@ func (f *controllerFile) SetStat(ctx context.Context, fs *vfs.Filesystem, creds
return f.InodeAttrs.SetStat(ctx, fs, creds, opts)
}
func (fs *filesystem) newControllerFile(ctx context.Context, creds *auth.Credentials, data vfs.DynamicBytesSource) kernfs.Inode {
f := &controllerFile{}
func (fs *filesystem) newControllerFile(ctx context.Context, creds *auth.Credentials, data vfs.DynamicBytesSource, allowBackgroundAccess bool) kernfs.Inode {
f := &controllerFile{
allowBackgroundAccess: allowBackgroundAccess,
}
f.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), data, readonlyFileMode)
return f
}
func (fs *filesystem) newControllerWritableFile(ctx context.Context, creds *auth.Credentials, data vfs.WritableDynamicBytesSource) kernfs.Inode {
f := &controllerFile{}
func (fs *filesystem) newControllerWritableFile(ctx context.Context, creds *auth.Credentials, data vfs.WritableDynamicBytesSource, allowBackgroundAccess bool) kernfs.Inode {
f := &controllerFile{
allowBackgroundAccess: allowBackgroundAccess,
}
f.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), data, writableFileMode)
return f
}
@@ -668,6 +726,18 @@ type staticControllerFile struct {
vfs.StaticData
}
var _ controllerFileImpl = (*staticControllerFile)(nil)
// Source implements controllerFileImpl.Source.
func (f *staticControllerFile) Source() *kernfs.DynamicBytesFile {
return &f.DynamicBytesFile
}
// AllowBackgroundAccess implements controllerFileImpl.AllowBackgroundAccess.
func (f *staticControllerFile) AllowBackgroundAccess() bool {
return true
}
// SetStat implements kernfs.Inode.SetStat.
func (f *staticControllerFile) SetStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
return f.InodeAttrs.SetStat(ctx, fs, creds, opts)
@@ -694,6 +764,8 @@ type stubControllerFile struct {
data *atomicbitops.Int64
}
var _ controllerFileImpl = (*stubControllerFile)(nil)
// Generate implements vfs.DynamicBytesSource.Generate.
func (f *stubControllerFile) Generate(ctx context.Context, buf *bytes.Buffer) error {
fmt.Fprintf(buf, "%d\n", f.data.Load())
@@ -702,6 +774,11 @@ func (f *stubControllerFile) Generate(ctx context.Context, buf *bytes.Buffer) er
// Write implements vfs.WritableDynamicBytesSource.Write.
func (f *stubControllerFile) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
return f.WriteBackground(ctx, src)
}
// WriteBackground implements writableControllerFileImpl.WriteBackground.
func (f *stubControllerFile) WriteBackground(ctx context.Context, src usermem.IOSequence) (int64, error) {
val, n, err := parseInt64FromString(ctx, src)
if err != nil {
return 0, err
@@ -712,8 +789,11 @@ func (f *stubControllerFile) Write(ctx context.Context, _ *vfs.FileDescription,
// newStubControllerFile creates a new stub controller file that loads and
// stores a control value from data.
func (fs *filesystem) newStubControllerFile(ctx context.Context, creds *auth.Credentials, data *atomicbitops.Int64) kernfs.Inode {
func (fs *filesystem) newStubControllerFile(ctx context.Context, creds *auth.Credentials, data *atomicbitops.Int64, allowBackgroundAccess bool) kernfs.Inode {
f := &stubControllerFile{
controllerFile: controllerFile{
allowBackgroundAccess: allowBackgroundAccess,
},
data: data,
}
f.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), f, writableFileMode)
+3 -3
View File
@@ -76,7 +76,7 @@ func (c *cpuController) Clone() controller {
// AddControlFiles implements controller.AddControlFiles.
func (c *cpuController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
contents["cpu.cfs_period_us"] = c.fs.newStubControllerFile(ctx, creds, &c.cfsPeriod)
contents["cpu.cfs_quota_us"] = c.fs.newStubControllerFile(ctx, creds, &c.cfsQuota)
contents["cpu.shares"] = c.fs.newStubControllerFile(ctx, creds, &c.shares)
contents["cpu.cfs_period_us"] = c.fs.newStubControllerFile(ctx, creds, &c.cfsPeriod, true)
contents["cpu.cfs_quota_us"] = c.fs.newStubControllerFile(ctx, creds, &c.cfsQuota, true)
contents["cpu.shares"] = c.fs.newStubControllerFile(ctx, creds, &c.shares, true)
}
+4 -4
View File
@@ -81,10 +81,10 @@ func (c *cpuacctController) Clone() controller {
// AddControlFiles implements controller.AddControlFiles.
func (c *cpuacctController) AddControlFiles(ctx context.Context, creds *auth.Credentials, cg *cgroupInode, contents map[string]kernfs.Inode) {
cpuacctCG := &cpuacctCgroup{cg}
contents["cpuacct.stat"] = c.fs.newControllerFile(ctx, creds, &cpuacctStatData{cpuacctCG})
contents["cpuacct.usage"] = c.fs.newControllerFile(ctx, creds, &cpuacctUsageData{cpuacctCG})
contents["cpuacct.usage_user"] = c.fs.newControllerFile(ctx, creds, &cpuacctUsageUserData{cpuacctCG})
contents["cpuacct.usage_sys"] = c.fs.newControllerFile(ctx, creds, &cpuacctUsageSysData{cpuacctCG})
contents["cpuacct.stat"] = c.fs.newControllerFile(ctx, creds, &cpuacctStatData{cpuacctCG}, true)
contents["cpuacct.usage"] = c.fs.newControllerFile(ctx, creds, &cpuacctUsageData{cpuacctCG}, true)
contents["cpuacct.usage_user"] = c.fs.newControllerFile(ctx, creds, &cpuacctUsageUserData{cpuacctCG}, true)
contents["cpuacct.usage_sys"] = c.fs.newControllerFile(ctx, creds, &cpuacctUsageSysData{cpuacctCG}, true)
}
// Enter implements controller.Enter.
+14 -6
View File
@@ -82,8 +82,8 @@ func (c *cpusetController) Clone() controller {
// AddControlFiles implements controller.AddControlFiles.
func (c *cpusetController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
contents["cpuset.cpus"] = c.fs.newControllerWritableFile(ctx, creds, &cpusData{c: c})
contents["cpuset.mems"] = c.fs.newControllerWritableFile(ctx, creds, &memsData{c: c})
contents["cpuset.cpus"] = c.fs.newControllerWritableFile(ctx, creds, &cpusData{c: c}, true)
contents["cpuset.mems"] = c.fs.newControllerWritableFile(ctx, creds, &memsData{c: c}, true)
}
// +stateify savable
@@ -101,12 +101,16 @@ func (d *cpusData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *cpusData) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
return d.WriteBackground(ctx, src)
}
// WriteBackground implements writableControllerFileImpl.WriteBackground.
func (d *cpusData) WriteBackground(ctx context.Context, src usermem.IOSequence) (int64, error) {
if src.NumBytes() > hostarch.PageSize {
return 0, linuxerr.EINVAL
}
t := kernel.TaskFromContext(ctx)
buf := t.CopyScratchBuffer(hostarch.PageSize)
buf := copyScratchBufferFromContext(ctx, hostarch.PageSize)
n, err := src.CopyIn(ctx, buf)
if err != nil {
return 0, err
@@ -145,12 +149,16 @@ func (d *memsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *memsData) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
return d.WriteBackground(ctx, src)
}
// WriteBackground implements writableControllerFileImpl.WriteBackground.
func (d *memsData) WriteBackground(ctx context.Context, src usermem.IOSequence) (int64, error) {
if src.NumBytes() > hostarch.PageSize {
return 0, linuxerr.EINVAL
}
t := kernel.TaskFromContext(ctx)
buf := t.CopyScratchBuffer(hostarch.PageSize)
buf := copyScratchBufferFromContext(ctx, hostarch.PageSize)
n, err := src.CopyIn(ctx, buf)
if err != nil {
return 0, err
+1 -1
View File
@@ -49,5 +49,5 @@ func (c *jobController) Clone() controller {
}
func (c *jobController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
contents["job.id"] = c.fs.newStubControllerFile(ctx, creds, &c.id)
contents["job.id"] = c.fs.newStubControllerFile(ctx, creds, &c.id, true)
}
+4 -4
View File
@@ -80,10 +80,10 @@ func (c *memoryController) Clone() controller {
// AddControlFiles implements controller.AddControlFiles.
func (c *memoryController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
contents["memory.usage_in_bytes"] = c.fs.newControllerFile(ctx, creds, &memoryUsageInBytesData{})
contents["memory.limit_in_bytes"] = c.fs.newStubControllerFile(ctx, creds, &c.limitBytes)
contents["memory.soft_limit_in_bytes"] = c.fs.newStubControllerFile(ctx, creds, &c.softLimitBytes)
contents["memory.move_charge_at_immigrate"] = c.fs.newStubControllerFile(ctx, creds, &c.moveChargeAtImmigrate)
contents["memory.usage_in_bytes"] = c.fs.newControllerFile(ctx, creds, &memoryUsageInBytesData{}, true)
contents["memory.limit_in_bytes"] = c.fs.newStubControllerFile(ctx, creds, &c.limitBytes, true)
contents["memory.soft_limit_in_bytes"] = c.fs.newStubControllerFile(ctx, creds, &c.softLimitBytes, true)
contents["memory.move_charge_at_immigrate"] = c.fs.newStubControllerFile(ctx, creds, &c.moveChargeAtImmigrate, true)
contents["memory.pressure_level"] = c.fs.newStaticControllerFile(ctx, creds, linux.FileMode(0644), fmt.Sprintf("%d\n", c.pressureLevel))
}
+8 -4
View File
@@ -112,11 +112,11 @@ func (c *pidsController) Clone() controller {
// AddControlFiles implements controller.AddControlFiles.
func (c *pidsController) AddControlFiles(ctx context.Context, creds *auth.Credentials, _ *cgroupInode, contents map[string]kernfs.Inode) {
contents["pids.current"] = c.fs.newControllerFile(ctx, creds, &pidsCurrentData{c: c})
contents["pids.current"] = c.fs.newControllerFile(ctx, creds, &pidsCurrentData{c: c}, true)
if !c.isRoot {
// "This is not available in the root cgroup for obvious reasons" --
// Linux, Documentation/cgroup-v1/pids.txt.
contents["pids.max"] = c.fs.newControllerWritableFile(ctx, creds, &pidsMaxData{c: c})
contents["pids.max"] = c.fs.newControllerWritableFile(ctx, creds, &pidsMaxData{c: c}, true)
}
}
@@ -268,8 +268,12 @@ func (d *pidsMaxData) Generate(ctx context.Context, buf *bytes.Buffer) error {
// Write implements vfs.WritableDynamicBytesSource.Write.
func (d *pidsMaxData) Write(ctx context.Context, _ *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
t := kernel.TaskFromContext(ctx)
buf := t.CopyScratchBuffer(hostarch.PageSize)
return d.WriteBackground(ctx, src)
}
// WriteBackground implements writableControllerFileImpl.WriteBackground.
func (d *pidsMaxData) WriteBackground(ctx context.Context, src usermem.IOSequence) (int64, error) {
buf := copyScratchBufferFromContext(ctx, hostarch.PageSize)
ncpy, err := src.CopyIn(ctx, buf)
if err != nil {
return 0, err
@@ -44,7 +44,8 @@ type DynamicBytesFile struct {
locks vfs.FileLocks
// data can additionally implement vfs.WritableDynamicBytesSource to support
// writes.
// writes. This field cannot be changed to a different bytes source after
// Init.
data vfs.DynamicBytesSource
}
@@ -80,6 +81,11 @@ func (f *DynamicBytesFile) Locks() *vfs.FileLocks {
return &f.locks
}
// Data returns the underlying data source.
func (f *DynamicBytesFile) Data() vfs.DynamicBytesSource {
return f.data
}
// DynamicBytesFD implements vfs.FileDescriptionImpl for an FD backed by a
// DynamicBytesFile.
//
+79 -4
View File
@@ -22,6 +22,7 @@ import (
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/errors/linuxerr"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
"gvisor.dev/gvisor/pkg/sentry/vfs"
@@ -43,6 +44,26 @@ const (
CgroupControllerPIDs = CgroupControllerType("pids")
)
// ParseCgroupController parses a string as a CgroupControllerType.
func ParseCgroupController(val string) (CgroupControllerType, error) {
switch val {
case "cpu":
return CgroupControllerCPU, nil
case "cpuacct":
return CgroupControllerCPUAcct, nil
case "cpuset":
return CgroupControllerCPUSet, nil
case "job":
return CgroupControllerJob, nil
case "memory":
return CgroupControllerMemory, nil
case "pids":
return CgroupControllerPIDs, nil
default:
return "", fmt.Errorf("no such cgroup controller")
}
}
// CgroupResourceType represents a resource type tracked by a particular
// controller.
type CgroupResourceType int
@@ -69,9 +90,11 @@ type CgroupController interface {
// attached to. Returned value is valid for the lifetime of the controller.
HierarchyID() uint32
// RootCgroup returns the root cgroup for this controller. Returned value is
// valid for the lifetime of the controller.
RootCgroup() Cgroup
// EffectiveRootCgroup returns the effective root cgroup for this
// controller. This is either the actual root of the underlying cgroupfs
// filesystem, or the override root configured at sandbox startup. Returned
// value is valid for the lifetime of the controller.
EffectiveRootCgroup() Cgroup
// NumCgroups returns the number of cgroups managed by this controller.
// Returned value is a snapshot in time.
@@ -103,6 +126,18 @@ func (c *Cgroup) Path() string {
return c.FSLocalPath()
}
// Walk returns the cgroup at p, starting from c.
func (c *Cgroup) Walk(ctx context.Context, vfsObj *vfs.VirtualFilesystem, p fspath.Path) (Cgroup, error) {
d, err := c.Dentry.WalkDentryTree(ctx, vfsObj, p)
if err != nil {
return Cgroup{}, err
}
return Cgroup{
Dentry: d,
CgroupImpl: d.Inode().(CgroupImpl),
}, nil
}
// CgroupMigrationContext represents an in-flight cgroup migration for
// a single task.
type CgroupMigrationContext struct {
@@ -169,6 +204,14 @@ type CgroupImpl interface {
//
// See cgroupfs.controller.Charge.
Charge(t *Task, d *kernfs.Dentry, ctl CgroupControllerType, res CgroupResourceType, value int64) error
// ReadControlFromBackground allows a background context to read a cgroup's
// control values.
ReadControl(ctx context.Context, name string) (string, error)
// WriteControl allows a background context to write a cgroup's control
// values.
WriteControl(ctx context.Context, name string, val string) error
}
// hierarchy represents a cgroupfs filesystem instance, with a unique set of
@@ -210,6 +253,10 @@ type cgroupFS interface {
// filesystem creation. May only be called before the filesystem is visible
// to the vfs layer.
InitializeHierarchyID(hid uint32)
// RootCgroup returns the root cgroup of this instance. This returns the
// actual root, and ignores any overrides setting an effective root.
RootCgroup() Cgroup
}
// CgroupRegistry tracks the active set of cgroup controllers on the system.
@@ -316,6 +363,34 @@ func (r *CgroupRegistry) FindHierarchy(name string, ctypes []CgroupControllerTyp
return nil, nil
}
// FindCgroup locates a cgroup with the given parameters.
//
// A cgroup is considered a match even if it contains other controllers on the
// same hierarchy.
func (r *CgroupRegistry) FindCgroup(ctx context.Context, ctype CgroupControllerType, path string) (Cgroup, error) {
p := fspath.Parse(path)
if !p.Absolute {
return Cgroup{}, fmt.Errorf("path must be absolute")
}
k := KernelFromContext(ctx)
vfsfs, err := r.FindHierarchy("", []CgroupControllerType{ctype})
if err != nil {
return Cgroup{}, err
}
if vfsfs == nil {
return Cgroup{}, fmt.Errorf("controller not active")
}
rootCG := vfsfs.Impl().(cgroupFS).RootCgroup()
if !p.HasComponents() {
// Explicit root '/'.
return rootCG, nil
}
return rootCG.Walk(ctx, k.VFS(), p)
}
// Register registers the provided set of controllers with the registry as a new
// hierarchy. If any controller is already registered, the function returns an
// error without modifying the registry. Register sets the hierarchy ID for the
@@ -405,7 +480,7 @@ func (r *CgroupRegistry) computeInitialGroups(inherit map[Cgroup]struct{}) map[C
// ... and add the root cgroups of all the missing controllers.
for name, ctl := range r.controllers {
if _, ok := ctlSet[name]; !ok {
cg := ctl.RootCgroup()
cg := ctl.EffectiveRootCgroup()
// Multiple controllers may share the same hierarchy, so may have
// the same root cgroup. Grab a single ref per hierarchy root.
if _, ok := cgset[cg]; ok {
+7
View File
@@ -129,6 +129,12 @@ const (
UsageReduce = "Usage.Reduce"
)
// Commands for interacting with cgroupfs within the sandbox.
const (
CgroupsReadControlFiles = "Cgroups.ReadControlFiles"
CgroupsWriteControlFiles = "Cgroups.WriteControlFiles"
)
// ControlSocketAddr generates an abstract unix socket name for the given ID.
func ControlSocketAddr(id string) string {
return fmt.Sprintf("\x00runsc-sandbox.%s", id)
@@ -161,6 +167,7 @@ func newController(fd int, l *Loader) (*controller, error) {
srv: srv,
}
ctrl.srv.Register(ctrl.manager)
ctrl.srv.Register(&control.Cgroups{Kernel: l.k})
ctrl.srv.Register(&control.Lifecycle{Kernel: l.k})
ctrl.srv.Register(&control.Logging{})
ctrl.srv.Register(&control.Proc{Kernel: l.k})
+2
View File
@@ -95,6 +95,8 @@ func Main(version string) {
subcommands.Register(new(cmd.Statefile), debugGroup)
subcommands.Register(new(cmd.Symbolize), debugGroup)
subcommands.Register(new(cmd.Usage), debugGroup)
subcommands.Register(new(cmd.ReadControl), debugGroup)
subcommands.Register(new(cmd.WriteControl), debugGroup)
// Internal commands.
const internalGroup = "internal use only"
+2
View File
@@ -27,6 +27,7 @@ go_library(
"pause.go",
"platforms.go",
"ps.go",
"read_control.go",
"restore.go",
"resume.go",
"run.go",
@@ -38,6 +39,7 @@ go_library(
"syscalls.go",
"usage.go",
"wait.go",
"write_control.go",
],
visibility = [
"//runsc:__subpackages__",
+85
View File
@@ -0,0 +1,85 @@
// Copyright 2022 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 cmd
import (
"context"
"fmt"
"github.com/google/subcommands"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/flag"
)
// ReadControl implements subcommands.Command for the "read-control" command.
type ReadControl struct{}
// Name implements subcommands.Command.Name.
func (*ReadControl) Name() string {
return "read-control"
}
// Synopsis implements subcommands.Command.Synopsis.
func (*ReadControl) Synopsis() string {
return "read a cgroups control value inside the container"
}
// Usage implements subcommands.Command.Usage.
func (*ReadControl) Usage() string {
return `read-control <container-id> <controller> <cgroup-path> <control-value-name>
Where "<container-id>" is the name for the instance of the container,
"<controller>" is the name of an active cgroupv1 controller, <cgroup-path> is
the path to the cgroup to read and <control-value-name> is the name of the
control file to read.
EXAMPLE:
# runsc read-control <container-id> cpuacct / cpuacct.usage
`
}
// SetFlags implements subcommands.Command.SetFlags.
func (r *ReadControl) SetFlags(f *flag.FlagSet) {}
// Execute implements subcommands.Command.Execute.
func (r *ReadControl) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
if f.NArg() < 4 {
f.Usage()
return subcommands.ExitUsageError
}
id := f.Arg(0)
conf := args[0].(*config.Config)
c, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{})
if err != nil {
util.Fatalf("loading sandbox: %v", err)
}
out, err := c.Sandbox.CgroupsReadControlFile(control.CgroupControlFile{
Controller: f.Arg(1),
Path: f.Arg(2),
Name: f.Arg(3),
})
if err != nil {
fmt.Printf("ERROR: %s\n", err)
return subcommands.ExitFailure
}
fmt.Printf("%s\n", out)
return subcommands.ExitSuccess
}
+84
View File
@@ -0,0 +1,84 @@
// Copyright 2022 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 cmd
import (
"context"
"fmt"
"github.com/google/subcommands"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/runsc/cmd/util"
"gvisor.dev/gvisor/runsc/config"
"gvisor.dev/gvisor/runsc/container"
"gvisor.dev/gvisor/runsc/flag"
)
// WriteControl implements subcommands.Command for the "write-control" command.
type WriteControl struct{}
// Name implements subcommands.Command.Name.
func (*WriteControl) Name() string {
return "write-control"
}
// Synopsis implements subcommands.Command.Synopsis.
func (*WriteControl) Synopsis() string {
return "write a cgroups control value inside the container"
}
// Usage implements subcommands.Command.Usage.
func (*WriteControl) Usage() string {
return `write-control <container-id> <controller> <cgroup-path> <control-value-name> <data-to-write>
Where "<container-id>" is the name for the instance of the container,
"<controller>" is the name of an active cgroupv1 controller, <cgroup-path> is
the path to the cgroup to write and <control-value-name> is the name of the
control file to write.
EXAMPLE:
# runsc write-control <container-id> memory / memory.limit_in_bytes 536870912
`
}
// SetFlags implements subcommands.Command.SetFlags.
func (r *WriteControl) SetFlags(f *flag.FlagSet) {}
// Execute implements subcommands.Command.Execute.
func (r *WriteControl) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
if f.NArg() < 5 {
f.Usage()
return subcommands.ExitUsageError
}
id := f.Arg(0)
conf := args[0].(*config.Config)
c, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{})
if err != nil {
util.Fatalf("loading sandbox: %v", err)
}
err = c.Sandbox.CgroupsWriteControlFile(control.CgroupControlFile{
Controller: f.Arg(1),
Path: f.Arg(2),
Name: f.Arg(3),
}, f.Arg(4))
if err != nil {
fmt.Printf("ERROR: %s\n", err)
return subcommands.ExitFailure
}
return subcommands.ExitSuccess
}
+55
View File
@@ -1422,3 +1422,58 @@ func checkBinaryPermissions(conf *config.Config) error {
}
return nil
}
// CgroupsReadControlFile reads a single cgroupfs control file in the sandbox.
func (s *Sandbox) CgroupsReadControlFile(file control.CgroupControlFile) (string, error) {
log.Debugf("CgroupsReadControlFiles sandbox %q", s.ID)
conn, err := s.sandboxConnect()
if err != nil {
return "", err
}
defer conn.Close()
args := control.CgroupsReadArgs{
Args: []control.CgroupsReadArg{
{
File: file,
},
},
}
var out control.CgroupsResults
err = conn.Call(boot.CgroupsReadControlFiles, &args, &out)
if err != nil {
return "", err
}
if len(out.Results) != 1 {
return "", fmt.Errorf("expected 1 result, got %d, raw: %+v", len(out.Results), out)
}
return out.Results[0].Unpack()
}
// CgroupsWriteControlFile writes a single cgroupfs control file in the sandbox.
func (s *Sandbox) CgroupsWriteControlFile(file control.CgroupControlFile, value string) error {
log.Debugf("CgroupsReadControlFiles sandbox %q", s.ID)
conn, err := s.sandboxConnect()
if err != nil {
return err
}
defer conn.Close()
args := control.CgroupsWriteArgs{
Args: []control.CgroupsWriteArg{
{
File: file,
Value: value,
},
},
}
var out control.CgroupsResults
err = conn.Call(boot.CgroupsWriteControlFiles, &args, &out)
if err != nil {
return err
}
if len(out.Results) != 1 {
return fmt.Errorf("expected 1 result, got %d, raw: %+v", len(out.Results), out)
}
return out.Results[0].AsError()
}