mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
a13fbe75e4
commit
fc0e4d0a03
@@ -11,6 +11,7 @@ proto_library(
|
||||
go_library(
|
||||
name = "control",
|
||||
srcs = [
|
||||
"cgroups.go",
|
||||
"control.go",
|
||||
"events.go",
|
||||
"fs.go",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -32,6 +32,7 @@ message ControlConfig {
|
||||
PROC = 7;
|
||||
STATE = 8;
|
||||
DEBUG = 9;
|
||||
CGROUPS = 10;
|
||||
}
|
||||
|
||||
// allowed_controls represents which endpoints may be registered to the
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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__",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user