Files
zeekayandHanzo Dev 1fda956476 Set a call's files after its result, and check the magic
Reading a ZAP result writes every field its layout states, the file payload
included, so files attached before decoding were overwritten with none. A
frame whose header is not ZAP is refused at the magic rather than read as a
length.


Co-authored-by: Hanzo Dev <[email protected]>
2026-09-22 23:10:33 -07:00

288 lines
7.8 KiB
Go

// Copyright 2018 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 boot
import (
"fmt"
"strconv"
"strings"
"gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/sentry/control"
"gvisor.dev/gvisor/pkg/sentry/usage"
)
// NetworkInterface is the network statistics of the particular network interface
type NetworkInterface struct {
// Name is the name of the network interface.
Name string
RxBytes uint64
RxPackets uint64
RxErrors uint64
RxDropped uint64
TxBytes uint64
TxPackets uint64
TxErrors uint64
TxDropped uint64
}
// EventOut is the return type of the Event command.
type EventOut struct {
Event Event `json:"event"`
// ContainerUsage is each container's total CPU time.
ContainerUsage []Usage `json:"containerUsage"`
}
// Event struct for encoding the event data to JSON. Corresponds to runc's
// main.event struct.
type Event struct {
Type string `json:"type"`
ID string `json:"id"`
Data Stats `json:"data"`
}
// Stats is the runc specific stats structure for stability when encoding and
// decoding stats.
type Stats struct {
CPU CPU `json:"cpu"`
Memory Memory `json:"memory"`
Pids Pids `json:"pids"`
NetworkInterfaces []*NetworkInterface `json:"network_interfaces"`
}
// Pids contains stats on processes.
type Pids struct {
Current uint64 `json:"current,omitempty"`
Limit uint64 `json:"limit,omitempty"`
}
// MemoryEntry contains stats on a kind of memory.
type MemoryEntry struct {
Limit uint64 `json:"limit"`
Usage uint64 `json:"usage,omitempty"`
Max uint64 `json:"max,omitempty"`
Failcnt uint64 `json:"failcnt"`
}
// Memory contains stats on memory.
type Memory struct {
Cache uint64 `json:"cache,omitempty"`
Usage MemoryEntry `json:"usage,omitempty"`
Swap MemoryEntry `json:"swap,omitempty"`
Kernel MemoryEntry `json:"kernel,omitempty"`
KernelTCP MemoryEntry `json:"kernelTCP,omitempty"`
Raw []Stat `json:"raw,omitempty"`
}
// CPU contains stats on the CPU.
type CPU struct {
Usage CPUUsage `json:"usage"`
}
// CPUUsage contains stats on CPU usage.
type CPUUsage struct {
Kernel uint64 `json:"kernel,omitempty"`
User uint64 `json:"user,omitempty"`
Total uint64 `json:"total,omitempty"`
PerCPU []uint64 `json:"percpu,omitempty"`
}
func (cm *containerManager) readControlFile(file control.CgroupControlFile) (string, error) {
var out control.CgroupsResults
args := control.CgroupsReadArgs{
Args: []control.CgroupsReadArg{
{
File: file,
},
},
}
cgroups := control.Cgroups{Kernel: cm.l.k}
if err := cgroups.ReadControlFiles(&args, &out); 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()
}
func (cm *containerManager) getUsageFromCgroups(file control.CgroupControlFile) (uint64, error) {
val, err := cm.readControlFile(file)
if err != nil {
return 0, err
}
return strconv.ParseUint(val, 10, 64)
}
// Stat is one named value out of a raw memory stat file.
type Stat struct {
Name string `json:"name"`
Value uint64 `json:"value"`
}
// Usage is one container's total CPU time.
type Usage struct {
ContainerID string `json:"containerID"`
CPU uint64 `json:"cpu"`
}
// CPU is the container's total CPU time, or zero when it has none.
func (e *EventOut) CPU(containerID string) uint64 {
for _, u := range e.ContainerUsage {
if u.ContainerID == containerID {
return u.CPU
}
}
return 0
}
// usageOf restates a map of per-container CPU time as the list that crosses.
func usageOf(m map[string]uint64) []Usage {
us := make([]Usage, 0, len(m))
for id, cpu := range m {
us = append(us, Usage{ContainerID: id, CPU: cpu})
}
return us
}
// Event gets the events from the container.
func (cm *containerManager) Event(args *control.ContainerArgs, out *EventOut) error {
*out = EventOut{
Event: Event{
ID: args.ContainerID,
Type: "stats",
},
}
// PIDs and check that container exists before going further.
pids, err := cm.l.pidsCount(args.ContainerID)
if err != nil {
return err
}
out.Event.Data.Pids.Current = uint64(pids)
networkStats, err := cm.l.networkStats()
if err != nil {
return err
}
out.Event.Data.NetworkInterfaces = networkStats
numContainers := cm.l.containerCount()
if numContainers == 0 {
return fmt.Errorf("no container was found")
}
// Memory usage.
memFile := control.CgroupControlFile{
Controller: "memory",
Path: "/" + args.ContainerID,
Name: "memory.usage_in_bytes",
}
if cm.l.k.Cgroup2FS().EverMounted() {
memFile.Name = "memory.current"
}
memUsage, err := cm.getUsageFromCgroups(memFile)
if err != nil {
// Cgroups is not installed or there was an error to get usage
// from the cgroups. Fall back to the old method of getting the
// usage from the sentry.
log.Warningf("could not get container memory usage from cgroups, error: %v", err)
mem := cm.l.k.MemoryFile()
_ = mem.UpdateUsage(nil) // best effort to update.
_, totalUsage := usage.MemoryAccounting.Copy()
if numContainers == 1 {
memUsage = totalUsage
} else {
// In the multi-container case, reports 0 for the root (pause)
// container, since it's small and idle. Then equally split the
// usage to the other containers. At least the sum of all
// containers will correctly account for the memory used by the
// sandbox.
if args.ContainerID == cm.l.sandboxID {
memUsage = 0
} else {
memUsage = totalUsage / uint64(numContainers-1)
}
}
}
out.Event.Data.Memory.Usage.Usage = memUsage
// CPU usage by container.
cpu, err := cm.getCPUUsageFromCgroups()
out.ContainerUsage = usageOf(cpu)
if err != nil {
// Cgroups is not installed or there was an error to get usage
// from the cgroups. Fall back to the old method of getting the
// usage from the sentry and host cgroups.
log.Warningf("could not get container cpu usage from cgroups, error: %v", err)
out.ContainerUsage = usageOf(control.ContainerUsage(cm.l.k))
}
return nil
}
func (cm *containerManager) getCPUUsageFromCgroups() (map[string]uint64, error) {
usage := make(map[string]uint64)
cm.l.mu.Lock()
defer cm.l.mu.Unlock()
isV2 := cm.l.k.Cgroup2FS().EverMounted()
for name, cid := range cm.l.containerIDs {
// Before the introduction of cgroup v2, the container name, not the ID, was incorrectly used in
// the cgroup path. For avoiding behavior changes when cgroup v1 is in use, we use the container
// ID only for cgroup v2.
id := name
if isV2 {
id = cid
}
file := control.CgroupControlFile{
Controller: "cpuacct",
Path: "/" + id,
Name: "cpuacct.usage",
}
if isV2 {
file.Controller = "cpu"
file.Name = "cpu.stat"
}
valStr, err := cm.readControlFile(file)
if err != nil {
return nil, err
}
var cpuUsage uint64
if isV2 {
for _, line := range strings.Split(valStr, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "usage_usec ") {
usec, err := strconv.ParseUint(strings.TrimPrefix(line, "usage_usec "), 10, 64)
if err != nil {
return nil, fmt.Errorf("parsing usage_usec in cpu.stat: %w", err)
}
cpuUsage = usec * 1000
break
}
}
} else {
cpuUsage, err = strconv.ParseUint(valStr, 10, 64)
if err != nil {
return nil, err
}
}
usage[id] = cpuUsage
}
return usage, nil
}