mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
This change updates the security checks to align with Linux kernel behavior:
* Add ptrace_may_access-style checks (kernel.ContextCanTrace) when accessing
/proc/[pid]/maps and /proc/[pid]/smaps.
* Change the permissions of the /proc/[pid]/fd directory to 0500 (read/execute
for owner only).
* Add a similar kernel.ContextCanTrace check for /proc/[pid]/{environ,auxv}.
PiperOrigin-RevId: 842001353
573 lines
17 KiB
Go
573 lines
17 KiB
Go
// Copyright 2021 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 cgroupfs
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"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/log"
|
|
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
|
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
|
"gvisor.dev/gvisor/pkg/usermem"
|
|
)
|
|
|
|
// controllerCommon implements kernel.CgroupController.
|
|
//
|
|
// Must call init before use.
|
|
//
|
|
// +stateify savable
|
|
type controllerCommon struct {
|
|
ty kernel.CgroupControllerType
|
|
fs *filesystem
|
|
// parent is the parent controller if any. Immutable.
|
|
//
|
|
// Note that we don't have to update this on renames, since cgroup
|
|
// directories can't be moved to a different parent directory.
|
|
parent controller
|
|
}
|
|
|
|
func (c *controllerCommon) init(ty kernel.CgroupControllerType, fs *filesystem) {
|
|
c.ty = ty
|
|
c.fs = fs
|
|
}
|
|
|
|
func (c *controllerCommon) cloneFromParent(parent controller) {
|
|
c.ty = parent.Type()
|
|
c.fs = parent.Filesystem()
|
|
c.parent = parent
|
|
}
|
|
|
|
// Filesystem implements controller.Filesystem.
|
|
func (c *controllerCommon) Filesystem() *filesystem {
|
|
return c.fs
|
|
}
|
|
|
|
// Type implements kernel.CgroupController.Type.
|
|
func (c *controllerCommon) Type() kernel.CgroupControllerType {
|
|
return kernel.CgroupControllerType(c.ty)
|
|
}
|
|
|
|
// HierarchyID implements kernel.CgroupController.HierarchyID.
|
|
func (c *controllerCommon) HierarchyID() uint32 {
|
|
return c.fs.hierarchyID
|
|
}
|
|
|
|
// NumCgroups implements kernel.CgroupController.NumCgroups.
|
|
func (c *controllerCommon) NumCgroups() uint64 {
|
|
return c.fs.numCgroups.Load()
|
|
}
|
|
|
|
// Enabled implements kernel.CgroupController.Enabled.
|
|
//
|
|
// Controllers are currently always enabled.
|
|
func (c *controllerCommon) Enabled() bool {
|
|
return true
|
|
}
|
|
|
|
// 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.
|
|
// It is an extension of the public cgroup interface, containing cgroup
|
|
// functionality private to cgroupfs.
|
|
type controller interface {
|
|
kernel.CgroupController
|
|
|
|
// Filesystem returns the cgroupfs filesystem backing this controller.
|
|
Filesystem() *filesystem
|
|
|
|
// Clone creates a new controller based on the internal state of this
|
|
// controller. This is used to initialize a sub-cgroup based on the state of
|
|
// the parent.
|
|
Clone() controller
|
|
|
|
// AddControlFiles should extend the contents map with inodes representing
|
|
// control files defined by this controller.
|
|
AddControlFiles(ctx context.Context, creds *auth.Credentials, c *cgroupInode, contents map[string]kernfs.Inode)
|
|
|
|
// Enter is called when a task initially moves into a cgroup. This is
|
|
// distinct from migration because the task isn't migrating away from a
|
|
// cgroup. Enter is called when a task is created and joins its initial
|
|
// cgroup, or when cgroupfs is mounted and existing tasks are moved into
|
|
// cgroups.
|
|
Enter(t *kernel.Task)
|
|
|
|
// Leave is called when a task leaves a cgroup. This is distinct from
|
|
// migration because the task isn't migrating to another cgroup. Leave is
|
|
// called when a task exits.
|
|
Leave(t *kernel.Task)
|
|
|
|
// PrepareMigrate signals the controller that a migration is about to
|
|
// happen. The controller should check for any conditions that would prevent
|
|
// the migration. If PrepareMigrate succeeds, the controller must
|
|
// unconditionally either accept the migration via CommitMigrate, or roll it
|
|
// back via AbortMigrate.
|
|
//
|
|
// Postcondition: If PrepareMigrate returns nil, caller must resolve the
|
|
// migration by calling either CommitMigrate or AbortMigrate.
|
|
PrepareMigrate(t *kernel.Task, src controller) error
|
|
|
|
// CommitMigrate completes an in-flight migration.
|
|
//
|
|
// Precondition: Caller must call a corresponding PrepareMigrate.
|
|
CommitMigrate(t *kernel.Task, src controller)
|
|
|
|
// AbortMigrate cancels an in-flight migration.
|
|
//
|
|
// Precondition: Caller must call a corresponding PrepareMigrate.
|
|
AbortMigrate(t *kernel.Task, src controller)
|
|
|
|
// Charge charges a controller for a particular resource. The implementation
|
|
// should panic if passed a resource type they do not control.
|
|
Charge(t *kernel.Task, d *kernfs.Dentry, res kernel.CgroupResourceType, value int64) error
|
|
}
|
|
|
|
// cgroupInode implements kernel.CgroupImpl and kernfs.Inode.
|
|
//
|
|
// +stateify savable
|
|
type cgroupInode struct {
|
|
dir
|
|
|
|
// id is the id of this cgroup.
|
|
id uint32
|
|
|
|
// controllers is the set of controllers for this cgroup. This is used to
|
|
// store controller-specific state per cgroup. The set of controllers should
|
|
// match the controllers for this hierarchy as tracked by the filesystem
|
|
// object. Immutable.
|
|
controllers map[kernel.CgroupControllerType]controller
|
|
|
|
// ts is the list of tasks in this cgroup. The kernel is responsible for
|
|
// removing tasks from this list before they're destroyed, so any tasks on
|
|
// this list are always valid.
|
|
//
|
|
// ts, and cgroup membership in general is protected by fs.tasksMu.
|
|
ts map[*kernel.Task]struct{}
|
|
}
|
|
|
|
var _ kernel.CgroupImpl = (*cgroupInode)(nil)
|
|
|
|
func (fs *filesystem) newCgroupInode(ctx context.Context, creds *auth.Credentials, parent *cgroupInode, mode linux.FileMode) kernfs.Inode {
|
|
c := &cgroupInode{
|
|
dir: dir{fs: fs},
|
|
ts: make(map[*kernel.Task]struct{}),
|
|
controllers: make(map[kernel.CgroupControllerType]controller),
|
|
}
|
|
c.dir.cgi = c
|
|
|
|
k := kernel.KernelFromContext(ctx)
|
|
r := k.CgroupRegistry()
|
|
// Assign id for the cgroup.
|
|
cid, err := r.NextCgroupID()
|
|
if err != nil {
|
|
log.Warningf("cgroupfs newCgroupInode: Failed to assign id to the cgroup: %v", err)
|
|
}
|
|
c.id = cid
|
|
r.AddCgroup(c)
|
|
|
|
contents := make(map[string]kernfs.Inode)
|
|
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 {
|
|
new := ctl.Clone()
|
|
c.controllers[ty] = new
|
|
new.AddControlFiles(ctx, creds, c, contents)
|
|
}
|
|
} else {
|
|
for _, ctl := range fs.controllers {
|
|
// Uniqueness of controllers enforced by the filesystem on
|
|
// creation. The root cgroup uses the controllers directly from the
|
|
// filesystem.
|
|
c.controllers[ctl.Type()] = ctl
|
|
ctl.AddControlFiles(ctx, creds, c, contents)
|
|
}
|
|
}
|
|
|
|
c.dir.InodeAttrs.Init(ctx, creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), mode)
|
|
c.dir.OrderedChildren.Init(kernfs.OrderedChildrenOptions{Writable: true})
|
|
c.dir.IncLinks(c.dir.OrderedChildren.Populate(contents))
|
|
|
|
fs.numCgroups.Add(1)
|
|
|
|
return c
|
|
}
|
|
|
|
// HierarchyID implements kernel.CgroupImpl.HierarchyID.
|
|
func (c *cgroupInode) HierarchyID() uint32 {
|
|
return c.fs.hierarchyID
|
|
}
|
|
|
|
// Name implements kernel.CgroupImpl.Name.
|
|
func (c *cgroupInode) Name() string {
|
|
return c.fs.hierarchyName
|
|
}
|
|
|
|
// Controllers implements kernel.CgroupImpl.Controllers.
|
|
func (c *cgroupInode) Controllers() []kernel.CgroupController {
|
|
return c.fs.kcontrollers
|
|
}
|
|
|
|
// tasks returns a snapshot of the tasks inside the cgroup.
|
|
func (c *cgroupInode) tasks() []*kernel.Task {
|
|
c.fs.tasksMu.RLock()
|
|
defer c.fs.tasksMu.RUnlock()
|
|
|
|
ts := make([]*kernel.Task, 0, len(c.ts))
|
|
for t := range c.ts {
|
|
ts = append(ts, t)
|
|
}
|
|
return ts
|
|
}
|
|
|
|
// Enter implements kernel.CgroupImpl.Enter.
|
|
func (c *cgroupInode) Enter(t *kernel.Task) {
|
|
c.fs.tasksMu.Lock()
|
|
defer c.fs.tasksMu.Unlock()
|
|
|
|
c.ts[t] = struct{}{}
|
|
for _, ctl := range c.controllers {
|
|
ctl.Enter(t)
|
|
}
|
|
}
|
|
|
|
// Leave implements kernel.CgroupImpl.Leave.
|
|
func (c *cgroupInode) Leave(t *kernel.Task) {
|
|
c.fs.tasksMu.Lock()
|
|
defer c.fs.tasksMu.Unlock()
|
|
|
|
for _, ctl := range c.controllers {
|
|
ctl.Leave(t)
|
|
}
|
|
delete(c.ts, t)
|
|
}
|
|
|
|
// PrepareMigrate implements kernel.CgroupImpl.PrepareMigrate.
|
|
func (c *cgroupInode) PrepareMigrate(t *kernel.Task, src *kernel.Cgroup) error {
|
|
prepared := make([]controller, 0, len(c.controllers))
|
|
rollback := func() {
|
|
for _, p := range prepared {
|
|
c.controllers[p.Type()].AbortMigrate(t, p)
|
|
}
|
|
}
|
|
|
|
for srcType, srcCtl := range src.CgroupImpl.(*cgroupInode).controllers {
|
|
ctl := c.controllers[srcType]
|
|
if err := ctl.PrepareMigrate(t, srcCtl); err != nil {
|
|
rollback()
|
|
return err
|
|
}
|
|
prepared = append(prepared, srcCtl)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CommitMigrate implements kernel.CgroupImpl.CommitMigrate.
|
|
func (c *cgroupInode) CommitMigrate(t *kernel.Task, src *kernel.Cgroup) {
|
|
c.fs.tasksMu.Lock()
|
|
defer c.fs.tasksMu.Unlock()
|
|
|
|
for srcType, srcCtl := range src.CgroupImpl.(*cgroupInode).controllers {
|
|
c.controllers[srcType].CommitMigrate(t, srcCtl)
|
|
}
|
|
|
|
srcI := src.CgroupImpl.(*cgroupInode)
|
|
delete(srcI.ts, t)
|
|
c.ts[t] = struct{}{}
|
|
}
|
|
|
|
// AbortMigrate implements kernel.CgroupImpl.AbortMigrate.
|
|
func (c *cgroupInode) AbortMigrate(t *kernel.Task, src *kernel.Cgroup) {
|
|
for srcType, srcCtl := range src.CgroupImpl.(*cgroupInode).controllers {
|
|
c.controllers[srcType].AbortMigrate(t, srcCtl)
|
|
}
|
|
}
|
|
|
|
// CgroupFromControlFileFD returns a cgroup object given a control file FD for the cgroup.
|
|
func (c *cgroupInode) CgroupFromControlFileFD(fd *vfs.FileDescription) kernel.Cgroup {
|
|
controlFileDentry := fd.Dentry().Impl().(*kernfs.Dentry)
|
|
// The returned parent dentry remains valid without holding locks because in
|
|
// cgroupfs, the parent directory relationship of a control file is
|
|
// effectively immutable. Control files cannot be unlinked, renamed or
|
|
// destroyed independently from their parent directory.
|
|
parentD := controlFileDentry.Parent()
|
|
return kernel.Cgroup{
|
|
Dentry: parentD,
|
|
CgroupImpl: c,
|
|
}
|
|
}
|
|
|
|
// Charge implements kernel.CgroupImpl.Charge.
|
|
//
|
|
// Charge notifies a matching controller of a change in resource usage. Due to
|
|
// the uniqueness of controllers, at most one controller will match. If no
|
|
// matching controller is present in this directory, the call silently
|
|
// succeeds. The caller should call Charge on all hierarchies to ensure any
|
|
// matching controller across the entire system is charged.
|
|
func (c *cgroupInode) Charge(t *kernel.Task, d *kernfs.Dentry, ctlType kernel.CgroupControllerType, res kernel.CgroupResourceType, value int64) error {
|
|
c.fs.tasksMu.RLock()
|
|
defer c.fs.tasksMu.RUnlock()
|
|
if ctl, ok := c.controllers[ctlType]; ok {
|
|
return ctl.Charge(t, d, res, value)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadControl implements kernel.CgroupImpl.ReadControl.
|
|
func (c *cgroupInode) ReadControl(ctx context.Context, name string) (string, error) {
|
|
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
|
|
data, err := cbf.Source().Data(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
err = 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 {
|
|
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
|
|
}
|
|
|
|
// ID implements kernel.CgroupImpl.ID.
|
|
func (c *cgroupInode) ID() uint32 {
|
|
return c.id
|
|
}
|
|
|
|
func sortTIDs(tids []kernel.ThreadID) {
|
|
sort.Slice(tids, func(i, j int) bool { return tids[i] < tids[j] })
|
|
}
|
|
|
|
// +stateify savable
|
|
type cgroupProcsData struct {
|
|
*cgroupInode
|
|
}
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (d *cgroupProcsData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
t := kernel.TaskFromContext(ctx)
|
|
currPidns := t.ThreadGroup().PIDNamespace()
|
|
|
|
pgids := make(map[kernel.ThreadID]struct{})
|
|
|
|
for _, task := range d.tasks() {
|
|
// Map dedups pgid, since iterating over all tasks produces multiple
|
|
// entries for the group leaders.
|
|
if pgid := currPidns.IDOfThreadGroup(task.ThreadGroup()); pgid != 0 {
|
|
pgids[pgid] = struct{}{}
|
|
}
|
|
}
|
|
|
|
pgidList := make([]kernel.ThreadID, 0, len(pgids))
|
|
for pgid := range pgids {
|
|
pgidList = append(pgidList, pgid)
|
|
}
|
|
sortTIDs(pgidList)
|
|
|
|
for _, pgid := range pgidList {
|
|
fmt.Fprintf(buf, "%d\n", pgid)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Write implements vfs.WritableDynamicBytesSource.Write.
|
|
func (d *cgroupProcsData) Write(ctx context.Context, fd *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
|
tgid, n, err := parseInt64FromString(ctx, src)
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
t := kernel.TaskFromContext(ctx)
|
|
currPidns := t.ThreadGroup().PIDNamespace()
|
|
var targetTG *kernel.ThreadGroup
|
|
if tgid != 0 {
|
|
targetTG = currPidns.ThreadGroupWithID(kernel.ThreadID(tgid))
|
|
} else {
|
|
targetTG = t.ThreadGroup()
|
|
}
|
|
|
|
if targetTG == nil {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
return n, targetTG.MigrateCgroup(d.CgroupFromControlFileFD(fd))
|
|
}
|
|
|
|
// +stateify savable
|
|
type tasksData struct {
|
|
*cgroupInode
|
|
}
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (d *tasksData) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
t := kernel.TaskFromContext(ctx)
|
|
currPidns := t.ThreadGroup().PIDNamespace()
|
|
|
|
var pids []kernel.ThreadID
|
|
|
|
for _, task := range d.tasks() {
|
|
if pid := currPidns.IDOfTask(task); pid != 0 {
|
|
pids = append(pids, pid)
|
|
}
|
|
}
|
|
sortTIDs(pids)
|
|
|
|
for _, pid := range pids {
|
|
fmt.Fprintf(buf, "%d\n", pid)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Write implements vfs.WritableDynamicBytesSource.Write.
|
|
func (d *tasksData) Write(ctx context.Context, fd *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
|
tid, n, err := parseInt64FromString(ctx, src)
|
|
if err != nil {
|
|
return n, err
|
|
}
|
|
|
|
t := kernel.TaskFromContext(ctx)
|
|
currPidns := t.ThreadGroup().PIDNamespace()
|
|
var targetTask *kernel.Task
|
|
if tid != 0 {
|
|
targetTask = currPidns.TaskWithID(kernel.ThreadID(tid))
|
|
} else {
|
|
targetTask = t
|
|
}
|
|
if targetTask == nil {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
return n, targetTask.MigrateCgroup(d.CgroupFromControlFileFD(fd))
|
|
}
|
|
|
|
// parseInt64FromString interprets src as string encoding a int64 value, and
|
|
// returns the parsed value.
|
|
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
|
|
|
|
buf := copyScratchBufferFromContext(ctx, maxInt64StrLen)
|
|
n, err := src.CopyIn(ctx, buf)
|
|
if err != nil {
|
|
return 0, int64(n), err
|
|
}
|
|
str := strings.TrimSpace(string(buf[:n]))
|
|
|
|
val, err = strconv.ParseInt(str, 10, 64)
|
|
if err != nil {
|
|
// Note: This also handles zero-len writes if offset is beyond the end
|
|
// of src, or src is empty.
|
|
ctx.Debugf("cgroupfs.parseInt64FromString: failed to parse %q: %v", str, err)
|
|
return 0, int64(n), linuxerr.EINVAL
|
|
}
|
|
|
|
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{}
|
|
|
|
// Enter implements controller.Enter.
|
|
func (*controllerStateless) Enter(t *kernel.Task) {}
|
|
|
|
// Leave implements controller.Leave.
|
|
func (*controllerStateless) Leave(t *kernel.Task) {}
|
|
|
|
// PrepareMigrate implements controller.PrepareMigrate.
|
|
func (*controllerStateless) PrepareMigrate(t *kernel.Task, src controller) error {
|
|
return nil
|
|
}
|
|
|
|
// CommitMigrate implements controller.CommitMigrate.
|
|
func (*controllerStateless) CommitMigrate(t *kernel.Task, src controller) {}
|
|
|
|
// AbortMigrate implements controller.AbortMigrate.
|
|
func (*controllerStateless) AbortMigrate(t *kernel.Task, src controller) {}
|
|
|
|
// controllerNoResource partially implements controller. It stubs out the Charge
|
|
// method for controllers that don't track resource usage through the charge
|
|
// mechanism.
|
|
type controllerNoResource struct{}
|
|
|
|
// Charge implements controller.Charge.
|
|
func (*controllerNoResource) Charge(t *kernel.Task, d *kernfs.Dentry, res kernel.CgroupResourceType, value int64) error {
|
|
panic(fmt.Sprintf("cgroupfs: Attempted to charge a controller with unknown resource %v for value %v", res, value))
|
|
}
|