vendor: update c/common

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
This commit is contained in:
Giuseppe Scrivano
2023-11-02 12:48:36 +01:00
parent d2a37222b9
commit 33753db47b
22 changed files with 647 additions and 1250 deletions

5
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/containernetworking/cni v1.1.2
github.com/containernetworking/plugins v1.3.0
github.com/containers/buildah v1.32.1-0.20231026190652-11e3b2132761
github.com/containers/common v0.56.1-0.20231026130642-78e0a90c7c2f
github.com/containers/common v0.56.1-0.20231102181045-6a67921ec5ce
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/gvisor-tap-vsock v0.7.1
github.com/containers/image/v5 v5.28.1-0.20231101173728-373c52a9466f
@ -93,7 +93,7 @@ require (
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/containerd/cgroups/v3 v3.0.2 // indirect
github.com/containerd/containerd v1.7.7 // indirect
github.com/containerd/containerd v1.7.8 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
@ -212,6 +212,7 @@ require (
gopkg.in/go-jose/go-jose.v2 v2.6.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
tags.cncf.io/container-device-interface v0.6.2 // indirect
)
replace github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.1-0.20230904132852-a0466dd76f23

10
go.sum
View File

@ -195,8 +195,8 @@ github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09Zvgq
github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s=
github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g=
github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c=
github.com/containerd/containerd v1.7.7 h1:QOC2K4A42RQpcrZyptP6z9EJZnlHfHJUfZrAAHe15q4=
github.com/containerd/containerd v1.7.7/go.mod h1:3c4XZv6VeT9qgf9GMTxNTMFxGJrGpI2vz1yk4ye+YY8=
github.com/containerd/containerd v1.7.8 h1:RkwgOW3AVUT3H/dyT0W03Dc8AzlpMG65lX48KftOFSM=
github.com/containerd/containerd v1.7.8/go.mod h1:L/Hn9qylJtUFT7cPeM0Sr3fATj+WjHwRQ0lyrYk3OPY=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
@ -256,8 +256,8 @@ github.com/containernetworking/plugins v1.3.0 h1:QVNXMT6XloyMUoO2wUOqWTC1hWFV62Q
github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0=
github.com/containers/buildah v1.32.1-0.20231026190652-11e3b2132761 h1:MNE9Yk+sw3GhHGRIXQHqx4V3P9L2MVHrZITD107DDB4=
github.com/containers/buildah v1.32.1-0.20231026190652-11e3b2132761/go.mod h1:78sIy+6IjdfQWXfPUZyDqysufB/vhgz9SGLrLQ2k0KU=
github.com/containers/common v0.56.1-0.20231026130642-78e0a90c7c2f h1:dLevs+iNkMVt8kedSIymqTifYbsliivg/o31Zt0kkvk=
github.com/containers/common v0.56.1-0.20231026130642-78e0a90c7c2f/go.mod h1:pkkR/vqGja5F21okcBLwA2fiA1Hi7V2achYf9DId3X8=
github.com/containers/common v0.56.1-0.20231102181045-6a67921ec5ce h1:b0NLsUl+hvPYPiAlP7VJrSHJZDQbZgUa3i+JfwMv4To=
github.com/containers/common v0.56.1-0.20231102181045-6a67921ec5ce/go.mod h1:EOB29rKXAeQcUU8JQ9MjbYkyPfcNpAZ7s3Ar59PU0YE=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/gvisor-tap-vsock v0.7.1 h1:+Rc+sOPplrkQb/BUXeN0ug8TxjgyrIqo/9P/eNS2A4c=
@ -1654,3 +1654,5 @@ sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
src.elv.sh v0.16.0-rc1.0.20220116211855-fda62502ad7f h1:pjVeIo9Ba6K1Wy+rlwX91zT7A+xGEmxiNRBdN04gDTQ=
tags.cncf.io/container-device-interface v0.6.2 h1:dThE6dtp/93ZDGhqaED2Pu374SOeUkBfuvkLuiTdwzg=
tags.cncf.io/container-device-interface v0.6.2/go.mod h1:Shusyhjs1A5Na/kqPVLL0KqnHQHuunol9LFeUNkuGVE=

View File

@ -36,7 +36,7 @@ func (r *Runtime) setPlatformHostInfo(info *define.HostInfo) error {
}
// Get Map of all available controllers
availableControllers, err := cgroups.GetAvailableControllers(nil, unified)
availableControllers, err := cgroups.AvailableControllers(nil, unified)
if err != nil {
return fmt.Errorf("getting available cgroup controllers: %w", err)
}

View File

@ -92,7 +92,7 @@ func deleteSystemdCgroup(path string, resources *spec.LinuxResources) error {
return err
}
if rootless.IsRootless() {
conn, err := cgroups.GetUserConnection(rootless.GetRootlessUID())
conn, err := cgroups.UserConnection(rootless.GetRootlessUID())
if err != nil {
return err
}

View File

@ -65,7 +65,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
var preCPUStats CPUStats
if query.Stream {
preRead = time.Now()
systemUsage, _ := cgroups.GetSystemCPUUsage()
systemUsage, _ := cgroups.SystemCPUUsage()
preCPUStats = CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: stats.CPUNano,
@ -154,7 +154,7 @@ streamLabel: // A label to flatten the scope
memoryLimit = uint64(memInfo.MemTotal)
}
systemUsage, _ := cgroups.GetSystemCPUUsage()
systemUsage, _ := cgroups.SystemCPUUsage()
s := StatsJSON{
Stats: Stats{
Read: time.Now(),

View File

@ -26,7 +26,7 @@ func RunUnderSystemdScope(pid int, slice string, unitName string) error {
var err error
if rootless.IsRootless() {
conn, err = cgroups.GetUserConnection(rootless.GetRootlessUID())
conn, err = cgroups.UserConnection(rootless.GetRootlessUID())
if err != nil {
return err
}

View File

@ -1,151 +0,0 @@
//go:build !linux
// +build !linux
package cgroups
import (
"bufio"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
type blkioHandler struct{}
func getBlkioHandler() *blkioHandler {
return &blkioHandler{}
}
// Apply set the specified constraints
func (c *blkioHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
if res.BlockIO == nil {
return nil
}
return fmt.Errorf("blkio apply function not implemented yet")
}
// Create the cgroup
func (c *blkioHandler) Create(ctr *CgroupControl) (bool, error) {
if ctr.cgroup2 {
return false, nil
}
return ctr.createCgroupDirectory(Blkio)
}
// Destroy the cgroup
func (c *blkioHandler) Destroy(ctr *CgroupControl) error {
return rmDirRecursively(ctr.getCgroupv1Path(Blkio))
}
// Stat fills a metrics structure with usage stats for the controller
func (c *blkioHandler) Stat(ctr *CgroupControl, m *Metrics) error {
var ioServiceBytesRecursive []BlkIOEntry
if ctr.cgroup2 {
// more details on the io.stat file format:X https://facebookmicrosites.github.io/cgroup2/docs/io-controller.html
values, err := readCgroup2MapFile(ctr, "io.stat")
if err != nil {
return err
}
for k, v := range values {
d := strings.Split(k, ":")
if len(d) != 2 {
continue
}
minor, err := strconv.ParseUint(d[0], 10, 0)
if err != nil {
return err
}
major, err := strconv.ParseUint(d[1], 10, 0)
if err != nil {
return err
}
for _, item := range v {
d := strings.Split(item, "=")
if len(d) != 2 {
continue
}
op := d[0]
// Accommodate the cgroup v1 naming
switch op {
case "rbytes":
op = "read"
case "wbytes":
op = "write"
}
value, err := strconv.ParseUint(d[1], 10, 0)
if err != nil {
return err
}
entry := BlkIOEntry{
Op: op,
Major: major,
Minor: minor,
Value: value,
}
ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
}
}
} else {
BlkioRoot := ctr.getCgroupv1Path(Blkio)
p := filepath.Join(BlkioRoot, "blkio.throttle.io_service_bytes_recursive")
f, err := os.Open(p)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil
}
return fmt.Errorf("open %s: %w", p, err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) < 3 {
continue
}
d := strings.Split(parts[0], ":")
if len(d) != 2 {
continue
}
minor, err := strconv.ParseUint(d[0], 10, 0)
if err != nil {
return err
}
major, err := strconv.ParseUint(d[1], 10, 0)
if err != nil {
return err
}
op := parts[1]
value, err := strconv.ParseUint(parts[2], 10, 0)
if err != nil {
return err
}
entry := BlkIOEntry{
Op: op,
Major: major,
Minor: minor,
Value: value,
}
ioServiceBytesRecursive = append(ioServiceBytesRecursive, entry)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("parse %s: %w", p, err)
}
}
m.Blkio = BlkioMetrics{IoServiceBytesRecursive: ioServiceBytesRecursive}
return nil
}

View File

@ -1,614 +0,0 @@
//go:build !linux
// +build !linux
package cgroups
import (
"bufio"
"context"
"errors"
"fmt"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/containers/storage/pkg/unshare"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/godbus/dbus/v5"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
var (
// ErrCgroupDeleted means the cgroup was deleted
ErrCgroupDeleted = errors.New("cgroup deleted")
// ErrCgroupV1Rootless means the cgroup v1 were attempted to be used in rootless environment
ErrCgroupV1Rootless = errors.New("no support for CGroups V1 in rootless environments")
ErrStatCgroup = errors.New("no cgroup available for gathering user statistics")
)
// CgroupControl controls a cgroup hierarchy
type CgroupControl struct {
cgroup2 bool
path string
systemd bool
// List of additional cgroup subsystems joined that
// do not have a custom handler.
additionalControllers []controller
}
// CPUUsage keeps stats for the CPU usage (unit: nanoseconds)
type CPUUsage struct {
Kernel uint64
Total uint64
PerCPU []uint64
}
// MemoryUsage keeps stats for the memory usage
type MemoryUsage struct {
Usage uint64
Limit uint64
}
// CPUMetrics keeps stats for the CPU usage
type CPUMetrics struct {
Usage CPUUsage
}
// BlkIOEntry describes an entry in the blkio stats
type BlkIOEntry struct {
Op string
Major uint64
Minor uint64
Value uint64
}
// BlkioMetrics keeps usage stats for the blkio cgroup controller
type BlkioMetrics struct {
IoServiceBytesRecursive []BlkIOEntry
}
// MemoryMetrics keeps usage stats for the memory cgroup controller
type MemoryMetrics struct {
Usage MemoryUsage
}
// PidsMetrics keeps usage stats for the pids cgroup controller
type PidsMetrics struct {
Current uint64
}
// Metrics keeps usage stats for the cgroup controllers
type Metrics struct {
CPU CPUMetrics
Blkio BlkioMetrics
Memory MemoryMetrics
Pids PidsMetrics
}
type controller struct {
name string
symlink bool
}
type controllerHandler interface {
Create(*CgroupControl) (bool, error)
Apply(*CgroupControl, *spec.LinuxResources) error
Destroy(*CgroupControl) error
Stat(*CgroupControl, *Metrics) error
}
const (
cgroupRoot = "/sys/fs/cgroup"
// CPU is the cpu controller
CPU = "cpu"
// CPUAcct is the cpuacct controller
CPUAcct = "cpuacct"
// CPUset is the cpuset controller
CPUset = "cpuset"
// Memory is the memory controller
Memory = "memory"
// Pids is the pids controller
Pids = "pids"
// Blkio is the blkio controller
Blkio = "blkio"
)
var handlers map[string]controllerHandler
func init() {
handlers = make(map[string]controllerHandler)
handlers[CPU] = getCPUHandler()
handlers[CPUset] = getCpusetHandler()
handlers[Memory] = getMemoryHandler()
handlers[Pids] = getPidsHandler()
handlers[Blkio] = getBlkioHandler()
}
// getAvailableControllers get the available controllers
func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]controller, error) {
if cgroup2 {
controllers := []controller{}
controllersFile := cgroupRoot + "/cgroup.controllers"
// rootless cgroupv2: check available controllers for current user, systemd or servicescope will inherit
if unshare.IsRootless() {
userSlice, err := getCgroupPathForCurrentProcess()
if err != nil {
return controllers, err
}
// userSlice already contains '/' so not adding here
basePath := cgroupRoot + userSlice
controllersFile = fmt.Sprintf("%s/cgroup.controllers", basePath)
}
controllersFileBytes, err := os.ReadFile(controllersFile)
if err != nil {
return nil, fmt.Errorf("failed while reading controllers for cgroup v2: %w", err)
}
for _, controllerName := range strings.Fields(string(controllersFileBytes)) {
c := controller{
name: controllerName,
symlink: false,
}
controllers = append(controllers, c)
}
return controllers, nil
}
subsystems, _ := cgroupV1GetAllSubsystems()
controllers := []controller{}
// cgroupv1 and rootless: No subsystem is available: delegation is unsafe.
if unshare.IsRootless() {
return controllers, nil
}
for _, name := range subsystems {
if _, found := exclude[name]; found {
continue
}
fileInfo, err := os.Stat(cgroupRoot + "/" + name)
if err != nil {
continue
}
c := controller{
name: name,
symlink: !fileInfo.IsDir(),
}
controllers = append(controllers, c)
}
return controllers, nil
}
// GetAvailableControllers get string:bool map of all the available controllers
func GetAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]string, error) {
availableControllers, err := getAvailableControllers(exclude, cgroup2)
if err != nil {
return nil, err
}
controllerList := []string{}
for _, controller := range availableControllers {
controllerList = append(controllerList, controller.name)
}
return controllerList, nil
}
func cgroupV1GetAllSubsystems() ([]string, error) {
f, err := os.Open("/proc/cgroups")
if err != nil {
return nil, err
}
defer f.Close()
subsystems := []string{}
s := bufio.NewScanner(f)
for s.Scan() {
text := s.Text()
if text[0] != '#' {
parts := strings.Fields(text)
if len(parts) >= 4 && parts[3] != "0" {
subsystems = append(subsystems, parts[0])
}
}
}
if err := s.Err(); err != nil {
return nil, err
}
return subsystems, nil
}
func getCgroupPathForCurrentProcess() (string, error) {
path := fmt.Sprintf("/proc/%d/cgroup", os.Getpid())
f, err := os.Open(path)
if err != nil {
return "", err
}
defer f.Close()
cgroupPath := ""
s := bufio.NewScanner(f)
for s.Scan() {
text := s.Text()
procEntries := strings.SplitN(text, "::", 2)
// set process cgroupPath only if entry is valid
if len(procEntries) > 1 {
cgroupPath = procEntries[1]
}
}
if err := s.Err(); err != nil {
return cgroupPath, err
}
return cgroupPath, nil
}
// getCgroupv1Path is a helper function to get the cgroup v1 path
func (c *CgroupControl) getCgroupv1Path(name string) string {
return filepath.Join(cgroupRoot, name, c.path)
}
// initialize initializes the specified hierarchy
func (c *CgroupControl) initialize() (err error) {
createdSoFar := map[string]controllerHandler{}
defer func() {
if err != nil {
for name, ctr := range createdSoFar {
if err := ctr.Destroy(c); err != nil {
logrus.Warningf("error cleaning up controller %s for %s", name, c.path)
}
}
}
}()
if c.cgroup2 {
if err := createCgroupv2Path(filepath.Join(cgroupRoot, c.path)); err != nil {
return fmt.Errorf("creating cgroup path %s: %w", c.path, err)
}
}
for name, handler := range handlers {
created, err := handler.Create(c)
if err != nil {
return err
}
if created {
createdSoFar[name] = handler
}
}
if !c.cgroup2 {
// We won't need to do this for cgroup v2
for _, ctr := range c.additionalControllers {
if ctr.symlink {
continue
}
path := c.getCgroupv1Path(ctr.name)
if err := os.MkdirAll(path, 0o755); err != nil {
return fmt.Errorf("creating cgroup path for %s: %w", ctr.name, err)
}
}
}
return nil
}
func readFileAsUint64(path string) (uint64, error) {
data, err := os.ReadFile(path)
if err != nil {
return 0, err
}
v := cleanString(string(data))
if v == "max" {
return math.MaxUint64, nil
}
ret, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return ret, fmt.Errorf("parse %s from %s: %w", v, path, err)
}
return ret, nil
}
func readFileByKeyAsUint64(path, key string) (uint64, error) {
content, err := os.ReadFile(path)
if err != nil {
return 0, err
}
for _, line := range strings.Split(string(content), "\n") {
fields := strings.SplitN(line, " ", 2)
if fields[0] == key {
v := cleanString(string(fields[1]))
if v == "max" {
return math.MaxUint64, nil
}
ret, err := strconv.ParseUint(v, 10, 64)
if err != nil {
return ret, fmt.Errorf("parse %s from %s: %w", v, path, err)
}
return ret, nil
}
}
return 0, fmt.Errorf("no key named %s from %s", key, path)
}
// New creates a new cgroup control
func New(path string, resources *spec.LinuxResources) (*CgroupControl, error) {
cgroup2, err := IsCgroup2UnifiedMode()
if err != nil {
return nil, err
}
control := &CgroupControl{
cgroup2: cgroup2,
path: path,
}
if !cgroup2 {
controllers, err := getAvailableControllers(handlers, false)
if err != nil {
return nil, err
}
control.additionalControllers = controllers
}
if err := control.initialize(); err != nil {
return nil, err
}
return control, nil
}
// NewSystemd creates a new cgroup control
func NewSystemd(path string) (*CgroupControl, error) {
cgroup2, err := IsCgroup2UnifiedMode()
if err != nil {
return nil, err
}
control := &CgroupControl{
cgroup2: cgroup2,
path: path,
systemd: true,
}
return control, nil
}
// Load loads an existing cgroup control
func Load(path string) (*CgroupControl, error) {
cgroup2, err := IsCgroup2UnifiedMode()
if err != nil {
return nil, err
}
control := &CgroupControl{
cgroup2: cgroup2,
path: path,
systemd: false,
}
if !cgroup2 {
controllers, err := getAvailableControllers(handlers, false)
if err != nil {
return nil, err
}
control.additionalControllers = controllers
}
if !cgroup2 {
oneExists := false
// check that the cgroup exists at least under one controller
for name := range handlers {
p := control.getCgroupv1Path(name)
if _, err := os.Stat(p); err == nil {
oneExists = true
break
}
}
// if there is no controller at all, raise an error
if !oneExists {
if unshare.IsRootless() {
return nil, ErrCgroupV1Rootless
}
// compatible with the error code
// used by containerd/cgroups
return nil, ErrCgroupDeleted
}
}
return control, nil
}
// CreateSystemdUnit creates the systemd cgroup
func (c *CgroupControl) CreateSystemdUnit(path string) error {
if !c.systemd {
return fmt.Errorf("the cgroup controller is not using systemd")
}
conn, err := systemdDbus.NewWithContext(context.TODO())
if err != nil {
return err
}
defer conn.Close()
return systemdCreate(path, conn)
}
// GetUserConnection returns a user connection to D-BUS
func GetUserConnection(uid int) (*systemdDbus.Conn, error) {
return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
return dbusAuthConnection(uid, dbus.SessionBusPrivateNoAutoStartup)
})
}
// CreateSystemdUserUnit creates the systemd cgroup for the specified user
func (c *CgroupControl) CreateSystemdUserUnit(path string, uid int) error {
if !c.systemd {
return fmt.Errorf("the cgroup controller is not using systemd")
}
conn, err := GetUserConnection(uid)
if err != nil {
return err
}
defer conn.Close()
return systemdCreate(path, conn)
}
func dbusAuthConnection(uid int, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) {
conn, err := createBus()
if err != nil {
return nil, err
}
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(uid))}
err = conn.Auth(methods)
if err != nil {
conn.Close()
return nil, err
}
if err := conn.Hello(); err != nil {
return nil, err
}
return conn, nil
}
// Delete cleans a cgroup
func (c *CgroupControl) Delete() error {
return c.DeleteByPath(c.path)
}
// DeleteByPathConn deletes the specified cgroup path using the specified
// dbus connection if needed.
func (c *CgroupControl) DeleteByPathConn(path string, conn *systemdDbus.Conn) error {
if c.systemd {
return systemdDestroyConn(path, conn)
}
if c.cgroup2 {
return rmDirRecursively(filepath.Join(cgroupRoot, c.path))
}
var lastError error
for _, h := range handlers {
if err := h.Destroy(c); err != nil {
lastError = err
}
}
for _, ctr := range c.additionalControllers {
if ctr.symlink {
continue
}
p := c.getCgroupv1Path(ctr.name)
if err := rmDirRecursively(p); err != nil {
lastError = fmt.Errorf("remove %s: %w", p, err)
}
}
return lastError
}
// DeleteByPath deletes the specified cgroup path
func (c *CgroupControl) DeleteByPath(path string) error {
if c.systemd {
conn, err := systemdDbus.NewWithContext(context.TODO())
if err != nil {
return err
}
defer conn.Close()
return c.DeleteByPathConn(path, conn)
}
return c.DeleteByPathConn(path, nil)
}
// Update updates the cgroups
func (c *CgroupControl) Update(resources *spec.LinuxResources) error {
for _, h := range handlers {
if err := h.Apply(c, resources); err != nil {
return err
}
}
return nil
}
// AddPid moves the specified pid to the cgroup
func (c *CgroupControl) AddPid(pid int) error {
pidString := []byte(fmt.Sprintf("%d\n", pid))
if c.cgroup2 {
p := filepath.Join(cgroupRoot, c.path, "cgroup.procs")
if err := os.WriteFile(p, pidString, 0o644); err != nil {
return fmt.Errorf("write %s: %w", p, err)
}
return nil
}
names := make([]string, 0, len(handlers))
for n := range handlers {
names = append(names, n)
}
for _, c := range c.additionalControllers {
if !c.symlink {
names = append(names, c.name)
}
}
for _, n := range names {
// If we aren't using cgroup2, we won't write correctly to unified hierarchy
if !c.cgroup2 && n == "unified" {
continue
}
p := filepath.Join(c.getCgroupv1Path(n), "tasks")
if err := os.WriteFile(p, pidString, 0o644); err != nil {
return fmt.Errorf("write %s: %w", p, err)
}
}
return nil
}
// Stat returns usage statistics for the cgroup
func (c *CgroupControl) Stat() (*Metrics, error) {
m := Metrics{}
found := false
for _, h := range handlers {
if err := h.Stat(c, &m); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, err
}
logrus.Warningf("Failed to retrieve cgroup stats: %v", err)
continue
}
found = true
}
if !found {
return nil, ErrStatCgroup
}
return &m, nil
}
func readCgroupMapPath(path string) (map[string][]string, error) {
ret := map[string][]string{}
f, err := os.Open(path)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return ret, nil
}
return nil, fmt.Errorf("open file %s: %w", path, err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) < 2 {
continue
}
ret[parts[0]] = parts[1:]
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("parsing file %s: %w", path, err)
}
return ret, nil
}
func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, error) {
p := filepath.Join(cgroupRoot, ctr.path, name)
return readCgroupMapPath(p)
}

View File

@ -5,6 +5,7 @@ package cgroups
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
@ -134,8 +135,8 @@ func getAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool)
return controllers, nil
}
// GetAvailableControllers get string:bool map of all the available controllers
func GetAvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]string, error) {
// AvailableControllers get string:bool map of all the available controllers
func AvailableControllers(exclude map[string]controllerHandler, cgroup2 bool) ([]string, error) {
availableControllers, err := getAvailableControllers(exclude, cgroup2)
if err != nil {
return nil, err
@ -391,20 +392,13 @@ func (c *CgroupControl) CreateSystemdUnit(path string) error {
return systemdCreate(c.config.Resources, path, conn)
}
// GetUserConnection returns an user connection to D-BUS
func GetUserConnection(uid int) (*systemdDbus.Conn, error) {
return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
return dbusAuthConnection(uid, dbus.SessionBusPrivateNoAutoStartup)
})
}
// CreateSystemdUserUnit creates the systemd cgroup for the specified user
func (c *CgroupControl) CreateSystemdUserUnit(path string, uid int) error {
if !c.systemd {
return fmt.Errorf("the cgroup controller is not using systemd")
}
conn, err := GetUserConnection(uid)
conn, err := UserConnection(uid)
if err != nil {
return err
}
@ -572,3 +566,170 @@ func readCgroup2MapFile(ctr *CgroupControl, name string) (map[string][]string, e
return readCgroupMapPath(p)
}
func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) {
cPath := c.getCgroupv1Path(controller)
_, err := os.Stat(cPath)
if err == nil {
return false, nil
}
if !errors.Is(err, os.ErrNotExist) {
return false, err
}
if err := os.MkdirAll(cPath, 0o755); err != nil {
return false, fmt.Errorf("creating cgroup for %s: %w", controller, err)
}
return true, nil
}
var TestMode bool
func createCgroupv2Path(path string) (deferredError error) {
if !strings.HasPrefix(path, cgroupRoot+"/") {
return fmt.Errorf("invalid cgroup path %s", path)
}
content, err := os.ReadFile(cgroupRoot + "/cgroup.controllers")
if err != nil {
return err
}
ctrs := bytes.Fields(content)
res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)
current := "/sys/fs"
elements := strings.Split(path, "/")
for i, e := range elements[3:] {
current = filepath.Join(current, e)
if i > 0 {
if err := os.Mkdir(current, 0o755); err != nil {
if !os.IsExist(err) {
return err
}
} else {
// If the directory was created, be sure it is not left around on errors.
defer func() {
if deferredError != nil {
os.Remove(current)
}
}()
}
}
// We enable the controllers for all the path components except the last one. It is not allowed to add
// PIDs if there are already enabled controllers.
if i < len(elements[3:])-1 {
if err := os.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0o755); err != nil {
return err
}
}
}
return nil
}
func cleanString(s string) string {
return strings.Trim(s, "\n")
}
func readAcct(ctr *CgroupControl, name string) (uint64, error) {
p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
return readFileAsUint64(p)
}
func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) {
p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
data, err := os.ReadFile(p)
if err != nil {
return nil, err
}
r := []uint64{}
for _, s := range strings.Split(string(data), " ") {
s = cleanString(s)
if s == "" {
break
}
v, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return nil, fmt.Errorf("parsing %s: %w", s, err)
}
r = append(r, v)
}
return r, nil
}
func cpusetCopyFromParent(path string, cgroupv2 bool) error {
for _, file := range []string{"cpuset.cpus", "cpuset.mems"} {
if _, err := cpusetCopyFileFromParent(path, file, cgroupv2); err != nil {
return err
}
}
return nil
}
func cpusetCopyFileFromParent(dir, file string, cgroupv2 bool) ([]byte, error) {
if dir == cgroupRoot {
return nil, fmt.Errorf("could not find parent to initialize cpuset %s", file)
}
path := filepath.Join(dir, file)
parentPath := path
if cgroupv2 {
parentPath = fmt.Sprintf("%s.effective", parentPath)
}
data, err := os.ReadFile(parentPath)
if err != nil {
// if the file doesn't exist, it is likely that the cpuset controller
// is not enabled in the kernel.
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
if strings.Trim(string(data), "\n") != "" {
return data, nil
}
data, err = cpusetCopyFileFromParent(filepath.Dir(dir), file, cgroupv2)
if err != nil {
return nil, err
}
if err := os.WriteFile(path, data, 0o644); err != nil {
return nil, fmt.Errorf("write %s: %w", path, err)
}
return data, nil
}
// SystemCPUUsage returns the system usage for all the cgroups
func SystemCPUUsage() (uint64, error) {
cgroupv2, err := IsCgroup2UnifiedMode()
if err != nil {
return 0, err
}
if !cgroupv2 {
p := filepath.Join(cgroupRoot, CPUAcct, "cpuacct.usage")
return readFileAsUint64(p)
}
files, err := os.ReadDir(cgroupRoot)
if err != nil {
return 0, err
}
var total uint64
for _, file := range files {
if !file.IsDir() {
continue
}
p := filepath.Join(cgroupRoot, file.Name(), "cpu.stat")
values, err := readCgroupMapPath(p)
if err != nil {
return 0, err
}
if val, found := values["usage_usec"]; found {
v, err := strconv.ParseUint(cleanString(val[0]), 10, 64)
if err != nil {
return 0, err
}
total += v * 1000
}
}
return total, nil
}

View File

@ -15,6 +15,8 @@ import (
"syscall"
"time"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
"github.com/godbus/dbus/v5"
"golang.org/x/sys/unix"
)
@ -37,6 +39,13 @@ func IsCgroup2UnifiedMode() (bool, error) {
return isUnified, isUnifiedErr
}
// UserConnection returns an user connection to D-BUS
func UserConnection(uid int) (*systemdDbus.Conn, error) {
return systemdDbus.NewConnection(func() (*dbus.Conn, error) {
return dbusAuthConnection(uid, dbus.SessionBusPrivateNoAutoStartup)
})
}
// UserOwnsCurrentSystemdCgroup checks whether the current EUID owns the
// current cgroup.
func UserOwnsCurrentSystemdCgroup() (bool, error) {

View File

@ -4,7 +4,10 @@
package cgroups
import (
"fmt"
"os"
systemdDbus "github.com/coreos/go-systemd/v22/dbus"
)
// IsCgroup2UnifiedMode returns whether we are running in cgroup 2 cgroup2 mode.
@ -21,3 +24,8 @@ func UserOwnsCurrentSystemdCgroup() (bool, error) {
func rmDirRecursively(path string) error {
return os.RemoveAll(path)
}
// UserConnection returns an user connection to D-BUS
func UserConnection(uid int) (*systemdDbus.Conn, error) {
return nil, fmt.Errorf("systemd d-bus is not supported on this platform")
}

View File

@ -1,91 +0,0 @@
//go:build !linux
// +build !linux
package cgroups
import (
"errors"
"fmt"
"os"
"strconv"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
type cpuHandler struct{}
func getCPUHandler() *cpuHandler {
return &cpuHandler{}
}
// Apply set the specified constraints
func (c *cpuHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
if res.CPU == nil {
return nil
}
return fmt.Errorf("cpu apply not implemented yet")
}
// Create the cgroup
func (c *cpuHandler) Create(ctr *CgroupControl) (bool, error) {
if ctr.cgroup2 {
return false, nil
}
return ctr.createCgroupDirectory(CPU)
}
// Destroy the cgroup
func (c *cpuHandler) Destroy(ctr *CgroupControl) error {
return rmDirRecursively(ctr.getCgroupv1Path(CPU))
}
// Stat fills a metrics structure with usage stats for the controller
func (c *cpuHandler) Stat(ctr *CgroupControl, m *Metrics) error {
var err error
usage := CPUUsage{}
if ctr.cgroup2 {
values, err := readCgroup2MapFile(ctr, "cpu.stat")
if err != nil {
return err
}
if val, found := values["usage_usec"]; found {
usage.Total, err = strconv.ParseUint(cleanString(val[0]), 10, 64)
if err != nil {
return err
}
usage.Kernel *= 1000
}
if val, found := values["system_usec"]; found {
usage.Kernel, err = strconv.ParseUint(cleanString(val[0]), 10, 64)
if err != nil {
return err
}
usage.Total *= 1000
}
// FIXME: How to read usage.PerCPU?
} else {
usage.Total, err = readAcct(ctr, "cpuacct.usage")
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return err
}
usage.Total = 0
}
usage.Kernel, err = readAcct(ctr, "cpuacct.usage_sys")
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return err
}
usage.Kernel = 0
}
usage.PerCPU, err = readAcctList(ctr, "cpuacct.usage_percpu")
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return err
}
usage.PerCPU = nil
}
}
m.CPU = CPUMetrics{Usage: usage}
return nil
}

View File

@ -1,49 +0,0 @@
//go:build !linux
// +build !linux
package cgroups
import (
"fmt"
"path/filepath"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
type cpusetHandler struct{}
func getCpusetHandler() *cpusetHandler {
return &cpusetHandler{}
}
// Apply set the specified constraints
func (c *cpusetHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
if res.CPU == nil {
return nil
}
return fmt.Errorf("cpuset apply not implemented yet")
}
// Create the cgroup
func (c *cpusetHandler) Create(ctr *CgroupControl) (bool, error) {
if ctr.cgroup2 {
path := filepath.Join(cgroupRoot, ctr.path)
return true, cpusetCopyFromParent(path, true)
}
created, err := ctr.createCgroupDirectory(CPUset)
if !created || err != nil {
return created, err
}
return true, cpusetCopyFromParent(ctr.getCgroupv1Path(CPUset), false)
}
// Destroy the cgroup
func (c *cpusetHandler) Destroy(ctr *CgroupControl) error {
return rmDirRecursively(ctr.getCgroupv1Path(CPUset))
}
// Stat fills a metrics structure with usage stats for the controller
func (c *cpusetHandler) Stat(ctr *CgroupControl, m *Metrics) error {
return nil
}

View File

@ -1,69 +0,0 @@
//go:build !linux
// +build !linux
package cgroups
import (
"fmt"
"path/filepath"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
type memHandler struct{}
func getMemoryHandler() *memHandler {
return &memHandler{}
}
// Apply set the specified constraints
func (c *memHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
if res.Memory == nil {
return nil
}
return fmt.Errorf("memory apply not implemented yet")
}
// Create the cgroup
func (c *memHandler) Create(ctr *CgroupControl) (bool, error) {
if ctr.cgroup2 {
return false, nil
}
return ctr.createCgroupDirectory(Memory)
}
// Destroy the cgroup
func (c *memHandler) Destroy(ctr *CgroupControl) error {
return rmDirRecursively(ctr.getCgroupv1Path(Memory))
}
// Stat fills a metrics structure with usage stats for the controller
func (c *memHandler) Stat(ctr *CgroupControl, m *Metrics) error {
var err error
usage := MemoryUsage{}
var memoryRoot string
var limitFilename string
if ctr.cgroup2 {
memoryRoot = filepath.Join(cgroupRoot, ctr.path)
limitFilename = "memory.max"
if usage.Usage, err = readFileByKeyAsUint64(filepath.Join(memoryRoot, "memory.stat"), "anon"); err != nil {
return err
}
} else {
memoryRoot = ctr.getCgroupv1Path(Memory)
limitFilename = "memory.limit_in_bytes"
if usage.Usage, err = readFileAsUint64(filepath.Join(memoryRoot, "memory.usage_in_bytes")); err != nil {
return err
}
}
usage.Limit, err = readFileAsUint64(filepath.Join(memoryRoot, limitFilename))
if err != nil {
return err
}
m.Memory = MemoryMetrics{Usage: usage}
return nil
}

View File

@ -1,71 +0,0 @@
//go:build !linux
// +build !linux
package cgroups
import (
"fmt"
"os"
"path/filepath"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
type pidHandler struct{}
func getPidsHandler() *pidHandler {
return &pidHandler{}
}
// Apply set the specified constraints
func (c *pidHandler) Apply(ctr *CgroupControl, res *spec.LinuxResources) error {
if res.Pids == nil {
return nil
}
var PIDRoot string
if ctr.cgroup2 {
PIDRoot = filepath.Join(cgroupRoot, ctr.path)
} else {
PIDRoot = ctr.getCgroupv1Path(Pids)
}
p := filepath.Join(PIDRoot, "pids.max")
return os.WriteFile(p, []byte(fmt.Sprintf("%d\n", res.Pids.Limit)), 0o644)
}
// Create the cgroup
func (c *pidHandler) Create(ctr *CgroupControl) (bool, error) {
if ctr.cgroup2 {
return false, nil
}
return ctr.createCgroupDirectory(Pids)
}
// Destroy the cgroup
func (c *pidHandler) Destroy(ctr *CgroupControl) error {
return rmDirRecursively(ctr.getCgroupv1Path(Pids))
}
// Stat fills a metrics structure with usage stats for the controller
func (c *pidHandler) Stat(ctr *CgroupControl, m *Metrics) error {
if ctr.path == "" {
// nothing we can do to retrieve the pids.current path
return nil
}
var PIDRoot string
if ctr.cgroup2 {
PIDRoot = filepath.Join(cgroupRoot, ctr.path)
} else {
PIDRoot = ctr.getCgroupv1Path(Pids)
}
current, err := readFileAsUint64(filepath.Join(PIDRoot, "pids.current"))
if err != nil {
return err
}
m.Pids = PidsMetrics{Current: current}
return nil
}

View File

@ -1,179 +0,0 @@
package cgroups
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
)
var TestMode bool
func cleanString(s string) string {
return strings.Trim(s, "\n")
}
func readAcct(ctr *CgroupControl, name string) (uint64, error) {
p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
return readFileAsUint64(p)
}
func readAcctList(ctr *CgroupControl, name string) ([]uint64, error) {
p := filepath.Join(ctr.getCgroupv1Path(CPUAcct), name)
data, err := os.ReadFile(p)
if err != nil {
return nil, err
}
r := []uint64{}
for _, s := range strings.Split(string(data), " ") {
s = cleanString(s)
if s == "" {
break
}
v, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return nil, fmt.Errorf("parsing %s: %w", s, err)
}
r = append(r, v)
}
return r, nil
}
// GetSystemCPUUsage returns the system usage for all the cgroups
func GetSystemCPUUsage() (uint64, error) {
cgroupv2, err := IsCgroup2UnifiedMode()
if err != nil {
return 0, err
}
if !cgroupv2 {
p := filepath.Join(cgroupRoot, CPUAcct, "cpuacct.usage")
return readFileAsUint64(p)
}
files, err := os.ReadDir(cgroupRoot)
if err != nil {
return 0, err
}
var total uint64
for _, file := range files {
if !file.IsDir() {
continue
}
p := filepath.Join(cgroupRoot, file.Name(), "cpu.stat")
values, err := readCgroupMapPath(p)
if err != nil {
return 0, err
}
if val, found := values["usage_usec"]; found {
v, err := strconv.ParseUint(cleanString(val[0]), 10, 64)
if err != nil {
return 0, err
}
total += v * 1000
}
}
return total, nil
}
func cpusetCopyFileFromParent(dir, file string, cgroupv2 bool) ([]byte, error) {
if dir == cgroupRoot {
return nil, fmt.Errorf("could not find parent to initialize cpuset %s", file)
}
path := filepath.Join(dir, file)
parentPath := path
if cgroupv2 {
parentPath = fmt.Sprintf("%s.effective", parentPath)
}
data, err := os.ReadFile(parentPath)
if err != nil {
// if the file doesn't exist, it is likely that the cpuset controller
// is not enabled in the kernel.
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
if strings.Trim(string(data), "\n") != "" {
return data, nil
}
data, err = cpusetCopyFileFromParent(filepath.Dir(dir), file, cgroupv2)
if err != nil {
return nil, err
}
if err := os.WriteFile(path, data, 0o644); err != nil {
return nil, fmt.Errorf("write %s: %w", path, err)
}
return data, nil
}
func cpusetCopyFromParent(path string, cgroupv2 bool) error {
for _, file := range []string{"cpuset.cpus", "cpuset.mems"} {
if _, err := cpusetCopyFileFromParent(path, file, cgroupv2); err != nil {
return err
}
}
return nil
}
// createCgroupv2Path creates the cgroupv2 path and enables all the available controllers
func createCgroupv2Path(path string) (deferredError error) {
if !strings.HasPrefix(path, cgroupRoot+"/") {
return fmt.Errorf("invalid cgroup path %s", path)
}
content, err := os.ReadFile(cgroupRoot + "/cgroup.controllers")
if err != nil {
return err
}
ctrs := bytes.Fields(content)
res := append([]byte("+"), bytes.Join(ctrs, []byte(" +"))...)
current := "/sys/fs"
elements := strings.Split(path, "/")
for i, e := range elements[3:] {
current = filepath.Join(current, e)
if i > 0 {
if err := os.Mkdir(current, 0o755); err != nil {
if !os.IsExist(err) {
return err
}
} else {
// If the directory was created, be sure it is not left around on errors.
defer func() {
if deferredError != nil {
os.Remove(current)
}
}()
}
}
// We enable the controllers for all the path components except the last one. It is not allowed to add
// PIDs if there are already enabled controllers.
if i < len(elements[3:])-1 {
if err := os.WriteFile(filepath.Join(current, "cgroup.subtree_control"), res, 0o755); err != nil {
return err
}
}
}
return nil
}
func (c *CgroupControl) createCgroupDirectory(controller string) (bool, error) {
cPath := c.getCgroupv1Path(controller)
_, err := os.Stat(cPath)
if err == nil {
return false, nil
}
if !errors.Is(err, os.ErrNotExist) {
return false, err
}
if err := os.MkdirAll(cPath, 0o755); err != nil {
return false, fmt.Errorf("creating cgroup for %s: %w", controller, err)
}
return true, nil
}

View File

@ -104,8 +104,8 @@ func ReadFile(dir, file string) (string, error) {
return buf.String(), err
}
// GetBlkioFiles gets the proper files for blkio weights
func GetBlkioFiles(cgroupPath string) (wtFile, wtDevFile string) {
// BlkioFiles gets the proper files for blkio weights
func BlkioFiles(cgroupPath string) (wtFile, wtDevFile string) {
var weightFile string
var weightDeviceFile string
// in this important since runc keeps these variables private, they won't be set

View File

@ -10,8 +10,8 @@ import (
"regexp"
"strings"
"github.com/container-orchestrated-devices/container-device-interface/pkg/parser"
units "github.com/docker/go-units"
"tags.cncf.io/container-device-interface/pkg/parser"
)
func (c *EngineConfig) validatePaths() error {

View File

@ -37,6 +37,30 @@ const (
)
var (
DefaultMaskedPaths = []string{
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/sched_debug",
"/proc/scsi",
"/proc/timer_list",
"/proc/timer_stats",
"/sys/dev/block",
"/sys/devices/virtual/powercap",
"/sys/firmware",
"/sys/fs/selinux",
}
DefaultReadOnlyPaths = []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
}
// DefaultInfraImage is the default image to run as infrastructure containers in pods.
DefaultInfraImage = ""
// DefaultRootlessSHMLockPath is the default path for rootless SHM locks.

7
vendor/modules.txt vendored
View File

@ -115,7 +115,7 @@ github.com/container-orchestrated-devices/container-device-interface/specs-go
# github.com/containerd/cgroups/v3 v3.0.2
## explicit; go 1.18
github.com/containerd/cgroups/v3/cgroup1/stats
# github.com/containerd/containerd v1.7.7
# github.com/containerd/containerd v1.7.8
## explicit; go 1.19
github.com/containerd/containerd/errdefs
github.com/containerd/containerd/log
@ -171,7 +171,7 @@ github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/pkg/volumes
github.com/containers/buildah/util
# github.com/containers/common v0.56.1-0.20231026130642-78e0a90c7c2f
# github.com/containers/common v0.56.1-0.20231102181045-6a67921ec5ce
## explicit; go 1.18
github.com/containers/common/internal/attributedstring
github.com/containers/common/libimage
@ -1353,4 +1353,7 @@ k8s.io/kubernetes/third_party/forked/golang/expansion
## explicit; go 1.12
sigs.k8s.io/yaml
sigs.k8s.io/yaml/goyaml.v2
# tags.cncf.io/container-device-interface v0.6.2
## explicit; go 1.19
tags.cncf.io/container-device-interface/pkg/parser
# github.com/opencontainers/runc => github.com/opencontainers/runc v1.1.1-0.20230904132852-a0466dd76f23

201
vendor/tags.cncf.io/container-device-interface/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,212 @@
/*
Copyright © The CDI 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 parser
import (
"fmt"
"strings"
)
// QualifiedName returns the qualified name for a device.
// The syntax for a qualified device names is
//
// "<vendor>/<class>=<name>".
//
// A valid vendor and class name may contain the following runes:
//
// 'A'-'Z', 'a'-'z', '0'-'9', '.', '-', '_'.
//
// A valid device name may contain the following runes:
//
// 'A'-'Z', 'a'-'z', '0'-'9', '-', '_', '.', ':'
func QualifiedName(vendor, class, name string) string {
return vendor + "/" + class + "=" + name
}
// IsQualifiedName tests if a device name is qualified.
func IsQualifiedName(device string) bool {
_, _, _, err := ParseQualifiedName(device)
return err == nil
}
// ParseQualifiedName splits a qualified name into device vendor, class,
// and name. If the device fails to parse as a qualified name, or if any
// of the split components fail to pass syntax validation, vendor and
// class are returned as empty, together with the verbatim input as the
// name and an error describing the reason for failure.
func ParseQualifiedName(device string) (string, string, string, error) {
vendor, class, name := ParseDevice(device)
if vendor == "" {
return "", "", device, fmt.Errorf("unqualified device %q, missing vendor", device)
}
if class == "" {
return "", "", device, fmt.Errorf("unqualified device %q, missing class", device)
}
if name == "" {
return "", "", device, fmt.Errorf("unqualified device %q, missing device name", device)
}
if err := ValidateVendorName(vendor); err != nil {
return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
}
if err := ValidateClassName(class); err != nil {
return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
}
if err := ValidateDeviceName(name); err != nil {
return "", "", device, fmt.Errorf("invalid device %q: %w", device, err)
}
return vendor, class, name, nil
}
// ParseDevice tries to split a device name into vendor, class, and name.
// If this fails, for instance in the case of unqualified device names,
// ParseDevice returns an empty vendor and class together with name set
// to the verbatim input.
func ParseDevice(device string) (string, string, string) {
if device == "" || device[0] == '/' {
return "", "", device
}
parts := strings.SplitN(device, "=", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", device
}
name := parts[1]
vendor, class := ParseQualifier(parts[0])
if vendor == "" {
return "", "", device
}
return vendor, class, name
}
// ParseQualifier splits a device qualifier into vendor and class.
// The syntax for a device qualifier is
//
// "<vendor>/<class>"
//
// If parsing fails, an empty vendor and the class set to the
// verbatim input is returned.
func ParseQualifier(kind string) (string, string) {
parts := strings.SplitN(kind, "/", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", kind
}
return parts[0], parts[1]
}
// ValidateVendorName checks the validity of a vendor name.
// A vendor name may contain the following ASCII characters:
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
// - digits ('0'-'9')
// - underscore, dash, and dot ('_', '-', and '.')
func ValidateVendorName(vendor string) error {
err := validateVendorOrClassName(vendor)
if err != nil {
err = fmt.Errorf("invalid vendor. %w", err)
}
return err
}
// ValidateClassName checks the validity of class name.
// A class name may contain the following ASCII characters:
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
// - digits ('0'-'9')
// - underscore, dash, and dot ('_', '-', and '.')
func ValidateClassName(class string) error {
err := validateVendorOrClassName(class)
if err != nil {
err = fmt.Errorf("invalid class. %w", err)
}
return err
}
// validateVendorOrClassName checks the validity of vendor or class name.
// A name may contain the following ASCII characters:
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
// - digits ('0'-'9')
// - underscore, dash, and dot ('_', '-', and '.')
func validateVendorOrClassName(name string) error {
if name == "" {
return fmt.Errorf("empty name")
}
if !IsLetter(rune(name[0])) {
return fmt.Errorf("%q, should start with letter", name)
}
for _, c := range string(name[1 : len(name)-1]) {
switch {
case IsAlphaNumeric(c):
case c == '_' || c == '-' || c == '.':
default:
return fmt.Errorf("invalid character '%c' in name %q",
c, name)
}
}
if !IsAlphaNumeric(rune(name[len(name)-1])) {
return fmt.Errorf("%q, should end with a letter or digit", name)
}
return nil
}
// ValidateDeviceName checks the validity of a device name.
// A device name may contain the following ASCII characters:
// - upper- and lowercase letters ('A'-'Z', 'a'-'z')
// - digits ('0'-'9')
// - underscore, dash, dot, colon ('_', '-', '.', ':')
func ValidateDeviceName(name string) error {
if name == "" {
return fmt.Errorf("invalid (empty) device name")
}
if !IsAlphaNumeric(rune(name[0])) {
return fmt.Errorf("invalid class %q, should start with a letter or digit", name)
}
if len(name) == 1 {
return nil
}
for _, c := range string(name[1 : len(name)-1]) {
switch {
case IsAlphaNumeric(c):
case c == '_' || c == '-' || c == '.' || c == ':':
default:
return fmt.Errorf("invalid character '%c' in device name %q",
c, name)
}
}
if !IsAlphaNumeric(rune(name[len(name)-1])) {
return fmt.Errorf("invalid name %q, should end with a letter or digit", name)
}
return nil
}
// IsLetter reports whether the rune is a letter.
func IsLetter(c rune) bool {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')
}
// IsDigit reports whether the rune is a digit.
func IsDigit(c rune) bool {
return '0' <= c && c <= '9'
}
// IsAlphaNumeric reports whether the rune is a letter or digit.
func IsAlphaNumeric(c rune) bool {
return IsLetter(c) || IsDigit(c)
}