![dependabot[bot]](/assets/img/avatar_default.png)
Bumps [github.com/containerd/cgroups/v3](https://github.com/containerd/cgroups) from 3.0.2 to 3.0.3. - [Release notes](https://github.com/containerd/cgroups/releases) - [Commits](https://github.com/containerd/cgroups/compare/v3.0.2...v3.0.3) --- updated-dependencies: - dependency-name: github.com/containerd/cgroups/v3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
268 lines
6.7 KiB
Go
268 lines
6.7 KiB
Go
package kconfig
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/cilium/ebpf/btf"
|
|
"github.com/cilium/ebpf/internal"
|
|
)
|
|
|
|
// Find find a kconfig file on the host.
|
|
// It first reads from /boot/config- of the current running kernel and tries
|
|
// /proc/config.gz if nothing was found in /boot.
|
|
// If none of the file provide a kconfig, it returns an error.
|
|
func Find() (*os.File, error) {
|
|
kernelRelease, err := internal.KernelRelease()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot get kernel release: %w", err)
|
|
}
|
|
|
|
path := "/boot/config-" + kernelRelease
|
|
f, err := os.Open(path)
|
|
if err == nil {
|
|
return f, nil
|
|
}
|
|
|
|
f, err = os.Open("/proc/config.gz")
|
|
if err == nil {
|
|
return f, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("neither %s nor /proc/config.gz provide a kconfig", path)
|
|
}
|
|
|
|
// Parse parses the kconfig file for which a reader is given.
|
|
// All the CONFIG_* which are in filter and which are set set will be
|
|
// put in the returned map as key with their corresponding value as map value.
|
|
// If filter is nil, no filtering will occur.
|
|
// If the kconfig file is not valid, error will be returned.
|
|
func Parse(source io.ReaderAt, filter map[string]struct{}) (map[string]string, error) {
|
|
var r io.Reader
|
|
zr, err := gzip.NewReader(io.NewSectionReader(source, 0, math.MaxInt64))
|
|
if err != nil {
|
|
r = io.NewSectionReader(source, 0, math.MaxInt64)
|
|
} else {
|
|
// Source is gzip compressed, transparently decompress.
|
|
r = zr
|
|
}
|
|
|
|
ret := make(map[string]string, len(filter))
|
|
|
|
s := bufio.NewScanner(r)
|
|
|
|
for s.Scan() {
|
|
line := s.Bytes()
|
|
err = processKconfigLine(line, ret, filter)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse line: %w", err)
|
|
}
|
|
|
|
if filter != nil && len(ret) == len(filter) {
|
|
break
|
|
}
|
|
}
|
|
|
|
if err := s.Err(); err != nil {
|
|
return nil, fmt.Errorf("cannot parse: %w", err)
|
|
}
|
|
|
|
if zr != nil {
|
|
return ret, zr.Close()
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// Golang translation of libbpf bpf_object__process_kconfig_line():
|
|
// https://github.com/libbpf/libbpf/blob/fbd60dbff51c870f5e80a17c4f2fd639eb80af90/src/libbpf.c#L1874
|
|
// It does the same checks but does not put the data inside the BPF map.
|
|
func processKconfigLine(line []byte, m map[string]string, filter map[string]struct{}) error {
|
|
// Ignore empty lines and "# CONFIG_* is not set".
|
|
if !bytes.HasPrefix(line, []byte("CONFIG_")) {
|
|
return nil
|
|
}
|
|
|
|
key, value, found := bytes.Cut(line, []byte{'='})
|
|
if !found {
|
|
return fmt.Errorf("line %q does not contain separator '='", line)
|
|
}
|
|
|
|
if len(value) == 0 {
|
|
return fmt.Errorf("line %q has no value", line)
|
|
}
|
|
|
|
if filter != nil {
|
|
// NB: map[string(key)] gets special optimisation help from the compiler
|
|
// and doesn't allocate. Don't turn this into a variable.
|
|
_, ok := filter[string(key)]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// This can seem odd, but libbpf only sets the value the first time the key is
|
|
// met:
|
|
// https://github.com/torvalds/linux/blob/0d85b27b0cc6/tools/lib/bpf/libbpf.c#L1906-L1908
|
|
_, ok := m[string(key)]
|
|
if !ok {
|
|
m[string(key)] = string(value)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// PutValue translates the value given as parameter depending on the BTF
|
|
// type, the translated value is then written to the byte array.
|
|
func PutValue(data []byte, typ btf.Type, value string) error {
|
|
typ = btf.UnderlyingType(typ)
|
|
|
|
switch value {
|
|
case "y", "n", "m":
|
|
return putValueTri(data, typ, value)
|
|
default:
|
|
if strings.HasPrefix(value, `"`) {
|
|
return putValueString(data, typ, value)
|
|
}
|
|
return putValueNumber(data, typ, value)
|
|
}
|
|
}
|
|
|
|
// Golang translation of libbpf_tristate enum:
|
|
// https://github.com/libbpf/libbpf/blob/fbd60dbff51c870f5e80a17c4f2fd639eb80af90/src/bpf_helpers.h#L169
|
|
type triState int
|
|
|
|
const (
|
|
TriNo triState = 0
|
|
TriYes triState = 1
|
|
TriModule triState = 2
|
|
)
|
|
|
|
func putValueTri(data []byte, typ btf.Type, value string) error {
|
|
switch v := typ.(type) {
|
|
case *btf.Int:
|
|
if v.Encoding != btf.Bool {
|
|
return fmt.Errorf("cannot add tri value, expected btf.Bool, got: %v", v.Encoding)
|
|
}
|
|
|
|
if v.Size != 1 {
|
|
return fmt.Errorf("cannot add tri value, expected size of 1 byte, got: %d", v.Size)
|
|
}
|
|
|
|
switch value {
|
|
case "y":
|
|
data[0] = 1
|
|
case "n":
|
|
data[0] = 0
|
|
default:
|
|
return fmt.Errorf("cannot use %q for btf.Bool", value)
|
|
}
|
|
case *btf.Enum:
|
|
if v.Name != "libbpf_tristate" {
|
|
return fmt.Errorf("cannot use enum %q, only libbpf_tristate is supported", v.Name)
|
|
}
|
|
|
|
var tri triState
|
|
switch value {
|
|
case "y":
|
|
tri = TriYes
|
|
case "m":
|
|
tri = TriModule
|
|
case "n":
|
|
tri = TriNo
|
|
default:
|
|
return fmt.Errorf("value %q is not support for libbpf_tristate", value)
|
|
}
|
|
|
|
internal.NativeEndian.PutUint64(data, uint64(tri))
|
|
default:
|
|
return fmt.Errorf("cannot add number value, expected btf.Int or btf.Enum, got: %T", v)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func putValueString(data []byte, typ btf.Type, value string) error {
|
|
array, ok := typ.(*btf.Array)
|
|
if !ok {
|
|
return fmt.Errorf("cannot add string value, expected btf.Array, got %T", array)
|
|
}
|
|
|
|
contentType, ok := btf.UnderlyingType(array.Type).(*btf.Int)
|
|
if !ok {
|
|
return fmt.Errorf("cannot add string value, expected array of btf.Int, got %T", contentType)
|
|
}
|
|
|
|
// Any Int, which is not bool, of one byte could be used to store char:
|
|
// https://github.com/torvalds/linux/blob/1a5304fecee5/tools/lib/bpf/libbpf.c#L3637-L3638
|
|
if contentType.Size != 1 && contentType.Encoding != btf.Bool {
|
|
return fmt.Errorf("cannot add string value, expected array of btf.Int of size 1, got array of btf.Int of size: %v", contentType.Size)
|
|
}
|
|
|
|
if !strings.HasPrefix(value, `"`) || !strings.HasSuffix(value, `"`) {
|
|
return fmt.Errorf(`value %q must start and finish with '"'`, value)
|
|
}
|
|
|
|
str := strings.Trim(value, `"`)
|
|
|
|
// We need to trim string if the bpf array is smaller.
|
|
if uint32(len(str)) >= array.Nelems {
|
|
str = str[:array.Nelems]
|
|
}
|
|
|
|
// Write the string content to .kconfig.
|
|
copy(data, str)
|
|
|
|
return nil
|
|
}
|
|
|
|
func putValueNumber(data []byte, typ btf.Type, value string) error {
|
|
integer, ok := typ.(*btf.Int)
|
|
if !ok {
|
|
return fmt.Errorf("cannot add number value, expected *btf.Int, got: %T", integer)
|
|
}
|
|
|
|
size := integer.Size
|
|
sizeInBits := size * 8
|
|
|
|
var n uint64
|
|
var err error
|
|
if integer.Encoding == btf.Signed {
|
|
parsed, e := strconv.ParseInt(value, 0, int(sizeInBits))
|
|
|
|
n = uint64(parsed)
|
|
err = e
|
|
} else {
|
|
parsed, e := strconv.ParseUint(value, 0, int(sizeInBits))
|
|
|
|
n = uint64(parsed)
|
|
err = e
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("cannot parse value: %w", err)
|
|
}
|
|
|
|
switch size {
|
|
case 1:
|
|
data[0] = byte(n)
|
|
case 2:
|
|
internal.NativeEndian.PutUint16(data, uint16(n))
|
|
case 4:
|
|
internal.NativeEndian.PutUint32(data, uint32(n))
|
|
case 8:
|
|
internal.NativeEndian.PutUint64(data, uint64(n))
|
|
default:
|
|
return fmt.Errorf("size (%d) is not valid, expected: 1, 2, 4 or 8", size)
|
|
}
|
|
|
|
return nil
|
|
}
|