whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user