mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
Fix a panic when writing to cgroup v2 control files from outside the sandbox (e.g., via Cgroups.WriteControlFiles / WriteControl), where writes occur without an open in-sandbox VFS file descriptor (fd == nil). cgroup.procs now safely falls back to context credentials with unconfined namespace migration, and checkNSDelegateWrite skips namespace delegation checks. Add an integration test for runsc cgroup v2 control file writes. Also: - Export cgroup2fs.InitHierarchy to initialize sandbox-wide cgroup2 state and root controller enablement. - Plumb CgroupNamespace through sentry lifecycle and task context to derive initial container cgroup2 placements. PiperOrigin-RevId: 978087817
538 lines
16 KiB
Go
538 lines
16 KiB
Go
// Copyright 2026 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 cgroup2fs
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/context"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"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"
|
|
)
|
|
|
|
// +stateify savable
|
|
type cgroupInterfaceFile struct {
|
|
kernfs.DynamicBytesFile
|
|
c *cgroup
|
|
}
|
|
|
|
// Valid implements kernfs.Inode.Valid.
|
|
func (f *cgroupInterfaceFile) Valid(ctx context.Context, parent *kernfs.Dentry, name string) bool {
|
|
return !f.c.deleted.Load()
|
|
}
|
|
|
|
// SetStat implements kernfs.Inode.SetStat.
|
|
func (f *cgroupInterfaceFile) SetStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
|
|
return f.InodeAttrs.SetStat(ctx, fs, creds, opts)
|
|
}
|
|
|
|
// Open implements kernfs.Inode.Open, overriding the DynamicBytesFile method
|
|
// to capture the opener's cgroup namespace.
|
|
func (f *cgroupInterfaceFile) Open(ctx context.Context, rp *vfs.ResolvingPath, d *kernfs.Dentry, opts vfs.OpenOptions) (*vfs.FileDescription, error) {
|
|
data, err := f.Data(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var ns *kernel.CgroupNamespace
|
|
if t := kernel.TaskFromContext(ctx); t != nil {
|
|
ns = t.GetCgroupNamespace() // ref taken
|
|
}
|
|
fd := &interfaceFD{ns: ns}
|
|
if err := fd.InitWithImpl(fd, rp.Mount(), d, data, f.Locks(), opts.Flags, rp.Credentials()); err != nil {
|
|
if ns != nil {
|
|
ns.DecRef(ctx)
|
|
}
|
|
return nil, err
|
|
}
|
|
return fd.VFSFileDescription(), nil
|
|
}
|
|
|
|
// interfaceFD implements vfs.FileDescriptionImpl for cgroup2 interface
|
|
// files. It is kernfs.DynamicBytesFD plus the opener's cgroup namespace.
|
|
//
|
|
// +stateify savable
|
|
type interfaceFD struct {
|
|
kernfs.DynamicBytesFD
|
|
|
|
// ns is the cgroup namespace of the task that opened this fd at open time.
|
|
ns *kernel.CgroupNamespace
|
|
}
|
|
|
|
// Release implements vfs.FileDescriptionImpl.Release.
|
|
func (fd *interfaceFD) Release(ctx context.Context) {
|
|
if fd.ns != nil {
|
|
fd.ns.DecRef(ctx)
|
|
}
|
|
fd.DynamicBytesFD.Release(ctx)
|
|
}
|
|
|
|
// +stateify savable
|
|
type cgroupSourceReadOnly struct {
|
|
c *cgroup
|
|
ctrl controller
|
|
src vfs.DynamicBytesSource
|
|
}
|
|
|
|
func (s *cgroupSourceReadOnly) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if s.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
if s.ctrl != nil && !s.ctrl.isActive() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
return s.src.Generate(ctx, buf)
|
|
}
|
|
|
|
// +stateify savable
|
|
type cgroupSourceWritable struct {
|
|
// The hosting cgroup.
|
|
c *cgroup
|
|
// The backing controller.
|
|
ctrl controller
|
|
// The backing writable source.
|
|
src vfs.WritableDynamicBytesSource
|
|
// Whether the source is delegatable to a child namespace.
|
|
nsDelegatable bool
|
|
}
|
|
|
|
func (s *cgroupSourceWritable) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if s.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
if s.ctrl != nil && !s.ctrl.isActive() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
return s.src.Generate(ctx, buf)
|
|
}
|
|
|
|
func (s *cgroupSourceWritable) Write(ctx context.Context, fd *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
|
if s.c.deleted.Load() {
|
|
return 0, linuxerr.ENODEV
|
|
}
|
|
if s.ctrl != nil && !s.ctrl.isActive() {
|
|
return 0, linuxerr.ENODEV
|
|
}
|
|
if !s.nsDelegatable {
|
|
if err := s.c.checkNSDelegateWrite(ctx, fd); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return s.src.Write(ctx, fd, src, offset)
|
|
}
|
|
|
|
func (fs *filesystem) newInode(ctx context.Context, uid auth.KUID, gid auth.KGID, c *cgroup, def interfaceFile) kernfs.Inode {
|
|
if def.isEvent {
|
|
eventFile := fs.newEventFile(ctx, uid, gid, c, def.source, def.ctrl)
|
|
if def.onEventCreated != nil {
|
|
def.onEventCreated(eventFile)
|
|
}
|
|
return eventFile
|
|
}
|
|
f := &cgroupInterfaceFile{c: c}
|
|
var src vfs.DynamicBytesSource
|
|
if ws, ok := def.source.(vfs.WritableDynamicBytesSource); ok {
|
|
src = &cgroupSourceWritable{c: c, ctrl: def.ctrl, src: ws, nsDelegatable: def.nsDelegatable}
|
|
} else {
|
|
src = &cgroupSourceReadOnly{c: c, ctrl: def.ctrl, src: def.source}
|
|
}
|
|
f.InitWithIDs(ctx, uid, gid, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), src, def.perm)
|
|
return f
|
|
}
|
|
|
|
func (fs *filesystem) rootInodes(ctx context.Context, uid auth.KUID, gid auth.KGID, c *cgroup) map[string]kernfs.Inode {
|
|
contents := make(map[string]kernfs.Inode)
|
|
contents["cgroup.procs"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.procs", source: &cgroupProcs{c: c}, perm: 0644, nsDelegatable: true})
|
|
contents["cgroup.controllers"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.controllers", source: &cgroupControllers{c: c}, perm: 0444})
|
|
contents["cgroup.subtree_control"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.subtree_control", source: &cgroupSubtreeControl{c: c}, perm: 0644, nsDelegatable: true})
|
|
contents["cgroup.max.descendants"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.max.descendants", source: &cgroupMaxDescendants{c: c}, perm: 0644})
|
|
contents["cgroup.max.depth"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.max.depth", source: &cgroupMaxDepth{c: c}, perm: 0644})
|
|
contents["cgroup.stat"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.stat", source: &cgroupStat{c: c}, perm: 0444})
|
|
return contents
|
|
}
|
|
|
|
func (fs *filesystem) cgroupInodes(ctx context.Context, uid auth.KUID, gid auth.KGID, c *cgroup) map[string]kernfs.Inode {
|
|
contents := make(map[string]kernfs.Inode)
|
|
contents["cgroup.procs"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.procs", source: &cgroupProcs{c: c}, perm: 0644, nsDelegatable: true})
|
|
contents["cgroup.controllers"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.controllers", source: &cgroupControllers{c: c}, perm: 0444})
|
|
contents["cgroup.subtree_control"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.subtree_control", source: &cgroupSubtreeControl{c: c}, perm: 0644, nsDelegatable: true})
|
|
contents["cgroup.max.descendants"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.max.descendants", source: &cgroupMaxDescendants{c: c}, perm: 0644})
|
|
contents["cgroup.max.depth"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.max.depth", source: &cgroupMaxDepth{c: c}, perm: 0644})
|
|
contents["cgroup.stat"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.stat", source: &cgroupStat{c: c}, perm: 0444})
|
|
contents["cgroup.type"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.type", source: &cgroupType{c: c}, perm: 0444})
|
|
contents["cgroup.kill"] = fs.newInode(ctx, uid, gid, c, interfaceFile{name: "cgroup.kill", source: &cgroupKill{c: c}, perm: 0200})
|
|
|
|
contents["cgroup.events"] = fs.newInode(ctx, uid, gid, c, interfaceFile{
|
|
name: "cgroup.events",
|
|
source: &cgroupEvents{c: c},
|
|
perm: 0444,
|
|
isEvent: true,
|
|
onEventCreated: func(inode *eventFile) {
|
|
c.eventsFile = inode
|
|
},
|
|
})
|
|
return contents
|
|
}
|
|
|
|
// cgroupProcs implements vfs.DynamicBytesSource for "cgroup.procs".
|
|
// +stateify savable
|
|
type cgroupProcs struct {
|
|
c *cgroup
|
|
}
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (cf *cgroupProcs) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if cf.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
var currPidns *kernel.PIDNamespace
|
|
if t := kernel.TaskFromContext(ctx); t != nil {
|
|
currPidns = t.PIDNamespace()
|
|
} else if k := kernel.KernelFromContext(ctx); k != nil {
|
|
currPidns = k.RootPIDNamespace()
|
|
}
|
|
pids := cf.c.getPIDsInNamespace(currPidns)
|
|
for _, pid := range pids {
|
|
fmt.Fprintf(buf, "%d\n", pid)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Write implements vfs.WritableDynamicBytesSource.Write.
|
|
func (cf *cgroupProcs) Write(ctx context.Context, fd *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
|
if cf.c.deleted.Load() {
|
|
return 0, linuxerr.ENODEV
|
|
}
|
|
data := make([]byte, src.NumBytes())
|
|
if _, err := src.CopyIn(ctx, data); err != nil {
|
|
return 0, err
|
|
}
|
|
str := strings.TrimSpace(string(data))
|
|
pid, err := strconv.ParseInt(str, 10, 64)
|
|
if err != nil || pid < 0 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
|
|
var nsRoot *cgroup
|
|
var creds *auth.Credentials
|
|
if fd != nil {
|
|
creds = fd.Credentials()
|
|
if ifd, ok := fd.Impl().(*interfaceFD); ok && ifd.ns != nil {
|
|
nsRoot = ifd.ns.Root().(*cgroup)
|
|
}
|
|
} else {
|
|
// If fd is nil, this write originates from outside the sandbox
|
|
// (e.g. via WriteControl or control RPCs) rather than through a
|
|
// sandboxed VFS file descriptor. Credentials come from ctx, and
|
|
// nsRoot remains nil so migration is unconfined by any cgroup
|
|
// namespace delegation boundary.
|
|
creds = auth.CredentialsFromContext(ctx)
|
|
}
|
|
if err := cf.c.attachProcess(ctx, creds, nsRoot, pid); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return src.NumBytes(), nil
|
|
}
|
|
|
|
// cgroupControllers implements vfs.DynamicBytesSource for "cgroup.controllers".
|
|
// +stateify savable
|
|
type cgroupControllers struct {
|
|
c *cgroup
|
|
}
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (cf *cgroupControllers) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if cf.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
var available []string
|
|
cf.c.fs.treeMu.RLock()
|
|
defer cf.c.fs.treeMu.RUnlock()
|
|
for ctl := firstController; ctl < numControllers; ctl++ {
|
|
if cf.c.isControllerAvailableLocked(ctl) {
|
|
available = append(available, ctrlTypeStr[ctl])
|
|
}
|
|
}
|
|
if len(available) > 0 {
|
|
buf.WriteString(strings.Join(available, " ") + "\n")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write implements vfs.WritableDynamicBytesSource.Write.
|
|
func (cf *cgroupControllers) Write(ctx context.Context, fd *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
|
|
// cgroupSubtreeControl implements vfs.DynamicBytesSource for "cgroup.subtree_control".
|
|
// It is used to enable or disable specific controllers for the cgroup's children.
|
|
// +stateify savable
|
|
type cgroupSubtreeControl struct {
|
|
c *cgroup
|
|
}
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (cf *cgroupSubtreeControl) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
var enabled []string
|
|
cf.c.fs.treeMu.RLock()
|
|
defer cf.c.fs.treeMu.RUnlock()
|
|
for ctl := firstController; ctl < numControllers; ctl++ {
|
|
if cf.c.subtreeCtrls[ctl] {
|
|
enabled = append(enabled, ctrlTypeStr[ctl])
|
|
}
|
|
}
|
|
if len(enabled) > 0 {
|
|
buf.WriteString(strings.Join(enabled, " ") + "\n")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write implements vfs.WritableDynamicBytesSource.Write.
|
|
func (cf *cgroupSubtreeControl) Write(ctx context.Context, fd *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
|
if cf.c.deleted.Load() {
|
|
return 0, linuxerr.ENODEV
|
|
}
|
|
data := make([]byte, src.NumBytes())
|
|
if _, err := src.CopyIn(ctx, data); err != nil {
|
|
return 0, err
|
|
}
|
|
str := strings.TrimSpace(string(data))
|
|
if str == "" {
|
|
return src.NumBytes(), nil
|
|
}
|
|
var enable []kernel.Cgroup2Ctrl
|
|
var disable []kernel.Cgroup2Ctrl
|
|
|
|
for _, ctrl := range strings.Split(str, " ") {
|
|
if ctrl == "" {
|
|
continue
|
|
}
|
|
if len(ctrl) < 2 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
op := ctrl[0]
|
|
name := ctrl[1:]
|
|
|
|
if op != '+' && op != '-' {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
cType, ok := ctrlNames[name]
|
|
if !ok {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
|
|
if op == '+' {
|
|
enable = append(enable, cType)
|
|
} else {
|
|
disable = append(disable, cType)
|
|
}
|
|
}
|
|
|
|
if err := cf.c.setSubtreeControl(ctx, enable, disable); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return src.NumBytes(), nil
|
|
}
|
|
|
|
// cgroupStat implements vfs.DynamicBytesSource for "cgroup.stat".
|
|
// +stateify savable
|
|
type cgroupStat struct {
|
|
c *cgroup
|
|
}
|
|
|
|
func (cf *cgroupStat) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if cf.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
cf.c.fs.treeMu.RLock()
|
|
defer cf.c.fs.treeMu.RUnlock()
|
|
cf.c.fs.tasksMu.RLock()
|
|
defer cf.c.fs.tasksMu.RUnlock()
|
|
descendants := cf.c.nrDescendants.Load()
|
|
fmt.Fprintf(buf, "nr_descendants %d\nnr_dying_descendants 0\n", descendants)
|
|
return nil
|
|
}
|
|
|
|
// cgroupType implements vfs.DynamicBytesSource for "cgroup.type".
|
|
// It identifies the cgroup namespace type.
|
|
// +stateify savable
|
|
type cgroupType struct {
|
|
c *cgroup
|
|
}
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (cf *cgroupType) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if cf.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
buf.WriteString("domain\n")
|
|
return nil
|
|
}
|
|
|
|
// +stateify savable
|
|
type cgroupMaxDescendants struct{ c *cgroup }
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (cf *cgroupMaxDescendants) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if cf.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
|
|
val := cf.c.maxDescendants.Load()
|
|
if val == limitMax {
|
|
buf.WriteString("max\n")
|
|
} else {
|
|
fmt.Fprintf(buf, "%d\n", val)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write implements vfs.WritableDynamicBytesSource.Write.
|
|
func (cf *cgroupMaxDescendants) Write(ctx context.Context, fd *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
|
if cf.c.deleted.Load() {
|
|
return 0, linuxerr.ENODEV
|
|
}
|
|
data := make([]byte, src.NumBytes())
|
|
if _, err := src.CopyIn(ctx, data); err != nil {
|
|
return 0, err
|
|
}
|
|
str := strings.TrimSpace(string(data))
|
|
var val int64
|
|
if str == "max" {
|
|
val = limitMax
|
|
} else {
|
|
var err error
|
|
val, err = strconv.ParseInt(str, 10, 64)
|
|
if err != nil || val < 0 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
}
|
|
|
|
cf.c.fs.treeMu.Lock()
|
|
defer cf.c.fs.treeMu.Unlock()
|
|
cf.c.maxDescendants.Store(val)
|
|
return src.NumBytes(), nil
|
|
}
|
|
|
|
// cgroupMaxDepth implements vfs.DynamicBytesSource for "cgroup.max.depth".
|
|
// +stateify savable
|
|
type cgroupMaxDepth struct{ c *cgroup }
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (cf *cgroupMaxDepth) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if cf.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
|
|
val := cf.c.maxDepth.Load()
|
|
if val == limitMax {
|
|
buf.WriteString("max\n")
|
|
} else {
|
|
fmt.Fprintf(buf, "%d\n", val)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write implements vfs.WritableDynamicBytesSource.Write.
|
|
func (cf *cgroupMaxDepth) Write(ctx context.Context, fd *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
|
if cf.c.deleted.Load() {
|
|
return 0, linuxerr.ENODEV
|
|
}
|
|
data := make([]byte, src.NumBytes())
|
|
if _, err := src.CopyIn(ctx, data); err != nil {
|
|
return 0, err
|
|
}
|
|
str := strings.TrimSpace(string(data))
|
|
var val int64
|
|
if str == "max" {
|
|
val = limitMax
|
|
} else {
|
|
var err error
|
|
val, err = strconv.ParseInt(str, 10, 64)
|
|
if err != nil || val < 0 {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
}
|
|
|
|
cf.c.fs.treeMu.Lock()
|
|
defer cf.c.fs.treeMu.Unlock()
|
|
cf.c.maxDepth.Store(val)
|
|
return src.NumBytes(), nil
|
|
}
|
|
|
|
// cgroupKill implements vfs.DynamicBytesSource for "cgroup.kill".
|
|
// +stateify savable
|
|
type cgroupKill struct{ c *cgroup }
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (cf *cgroupKill) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if cf.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Write implements vfs.WritableDynamicBytesSource.Write.
|
|
func (cf *cgroupKill) Write(ctx context.Context, fd *vfs.FileDescription, src usermem.IOSequence, offset int64) (int64, error) {
|
|
if cf.c.deleted.Load() {
|
|
return 0, linuxerr.ENODEV
|
|
}
|
|
data := make([]byte, src.NumBytes())
|
|
if _, err := src.CopyIn(ctx, data); err != nil {
|
|
return 0, err
|
|
}
|
|
str := strings.TrimSpace(string(data))
|
|
val, err := strconv.Atoi(str)
|
|
if err != nil {
|
|
return 0, linuxerr.EINVAL
|
|
}
|
|
if val != 1 {
|
|
return 0, linuxerr.ERANGE
|
|
}
|
|
if err := cf.c.kill(); err != nil {
|
|
return 0, err
|
|
}
|
|
return src.NumBytes(), nil
|
|
}
|
|
|
|
// cgroupEvents implements vfs.DynamicBytesSource for "cgroup.events".
|
|
// +stateify savable
|
|
type cgroupEvents struct {
|
|
c *cgroup
|
|
}
|
|
|
|
// Generate implements vfs.DynamicBytesSource.Generate.
|
|
func (cf *cgroupEvents) Generate(ctx context.Context, buf *bytes.Buffer) error {
|
|
if cf.c.deleted.Load() {
|
|
return linuxerr.ENODEV
|
|
}
|
|
populated := 0
|
|
if cf.c.populated() {
|
|
populated = 1
|
|
}
|
|
fmt.Fprintf(buf, "populated %d\nfrozen 0\n", populated)
|
|
return nil
|
|
}
|