whatcanGOwrong

This commit is contained in:
2024-09-19 21:38:24 -04:00
commit d0ae4d841d
17908 changed files with 4096831 additions and 0 deletions
@@ -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
}
@@ -0,0 +1,418 @@
package kconfig
import (
"bytes"
"encoding/binary"
"os"
"testing"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/internal"
qt "github.com/frankban/quicktest"
)
func BenchmarkParse(b *testing.B) {
f, err := os.Open("testdata/config-6.2.15-300.fc38.x86_64.gz")
if err != nil {
b.Fatal(err)
}
defer f.Close()
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := Parse(f, nil)
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkParseFiltered(b *testing.B) {
f, err := os.Open("testdata/config-6.2.15-300.fc38.x86_64.gz")
if err != nil {
b.Fatal(err)
}
defer f.Close()
b.ReportAllocs()
b.ResetTimer()
// CONFIG_ARCH_USE_MEMTEST is the last CONFIG_ in the file.
// So, we will easily be able to see how many allocated bytes the filtering
// permits reducing compared to unfiltered benchmark.
filter := map[string]struct{}{"CONFIG_ARCH_USE_MEMTEST": {}}
for n := 0; n < b.N; n++ {
_, err := Parse(f, filter)
if err != nil {
b.Fatal(err)
}
}
}
func TestParse(t *testing.T) {
t.Parallel()
f, err := os.Open("testdata/test.kconfig")
if err != nil {
t.Fatal("Error reading /testdata/test.kconfig: ", err)
}
defer f.Close()
config, err := Parse(f, nil)
if err != nil {
t.Fatal("Error parsing kconfig: ", err)
}
expected := map[string]string{
"CONFIG_TRISTATE": "m",
"CONFIG_BOOL": "y",
"CONFIG_CHAR": "100",
"CONFIG_USHORT": "30000",
"CONFIG_INT": "123456",
"CONFIG_ULONG": "0xDEADBEEFC0DE",
"CONFIG_STR": `"abracad"`,
"CONFIG_FOO": `"foo"`,
}
qt.Assert(t, config, qt.DeepEquals, expected)
}
func TestParseFiltered(t *testing.T) {
t.Parallel()
f, err := os.Open("testdata/test.kconfig")
if err != nil {
t.Fatal("Error reading /testdata/test.kconfig: ", err)
}
defer f.Close()
filter := map[string]struct{}{"CONFIG_FOO": {}}
config, err := Parse(f, filter)
if err != nil {
t.Fatal("Error parsing gzipped kconfig: ", err)
}
expected := map[string]string{"CONFIG_FOO": `"foo"`}
qt.Assert(t, config, qt.DeepEquals, expected)
}
func TestParseGzipped(t *testing.T) {
t.Parallel()
f, err := os.Open("testdata/config-6.2.15-300.fc38.x86_64.gz")
if err != nil {
t.Fatal("Error reading /testdata/config-6.2.15-300.fc38.x86_64.gz: ", err)
}
defer f.Close()
_, err = Parse(f, nil)
if err != nil {
t.Fatal("Error parsing gzipped kconfig: ", err)
}
}
func TestParseGzippedFiltered(t *testing.T) {
t.Parallel()
f, err := os.Open("testdata/config-6.2.15-300.fc38.x86_64.gz")
if err != nil {
t.Fatal("Error reading /testdata/config-6.2.15-300.fc38.x86_64.gz: ", err)
}
defer f.Close()
filter := map[string]struct{}{"CONFIG_HZ": {}}
config, err := Parse(f, filter)
if err != nil {
t.Fatal("Error parsing gzipped kconfig: ", err)
}
expected := map[string]string{"CONFIG_HZ": "1000"}
qt.Assert(t, config, qt.DeepEquals, expected)
}
func TestProcessKconfigBadLine(t *testing.T) {
t.Parallel()
m := make(map[string]string)
err := processKconfigLine([]byte("CONFIG_FOO"), m, nil)
qt.Assert(t, err, qt.IsNotNil, qt.Commentf("line has no '='"))
err = processKconfigLine([]byte("CONFIG_FOO="), m, nil)
qt.Assert(t, err, qt.IsNotNil, qt.Commentf("line has no value"))
}
func TestPutValue(t *testing.T) {
t.Parallel()
type testCase struct {
typ btf.Type
value string
expected any
comment string
}
cases := []testCase{
{
typ: &btf.Int{
Size: 1,
Encoding: btf.Bool,
},
value: "n",
expected: int8(0),
},
{
typ: &btf.Int{
Size: 1,
Encoding: btf.Bool,
},
value: "y",
expected: int8(1),
},
{
typ: &btf.Int{
Size: 1,
Encoding: btf.Bool,
},
value: "foo",
comment: "Bad value",
},
{
typ: &btf.Int{},
comment: "Encoding is not Bool",
},
{
typ: &btf.Int{
Encoding: btf.Bool,
},
comment: "Size is not 1",
},
{
typ: &btf.Enum{
Name: "libbpf_tristate",
},
value: "y",
expected: int64(TriYes),
},
{
typ: &btf.Enum{
Name: "libbpf_tristate",
},
value: "n",
expected: int64(TriNo),
},
{
typ: &btf.Enum{
Name: "libbpf_tristate",
},
value: "m",
expected: int64(TriModule),
},
{
typ: &btf.Enum{
Name: "libbpf_tristate",
},
value: "foo",
comment: "Bad value",
},
{
typ: &btf.Enum{
Name: "error",
},
comment: "Enum name is wrong",
},
{
typ: &btf.Array{},
value: "y",
comment: "Type is not btf.Int",
},
{
typ: &btf.Int{
Size: 1,
},
value: "255",
expected: uint8(255),
},
{
typ: &btf.Int{
Size: 2,
},
value: "0xcafe",
expected: uint16(0xcafe),
},
{
typ: &btf.Int{
Size: 2,
},
value: "0755",
expected: uint16(0755),
},
{
typ: &btf.Int{
Size: 4,
Encoding: btf.Signed,
},
value: "-2147483648",
expected: int32(-2147483648),
},
{
typ: &btf.Int{
Size: 4,
Encoding: btf.Signed,
},
value: "+2147483647",
expected: int32(+2147483647),
},
{
typ: &btf.Int{
Size: 4,
},
value: "0xcafec0de",
expected: uint32(0xcafec0de),
},
{
typ: &btf.Int{
Size: 8,
Encoding: btf.Signed,
},
value: "+1000000000000",
expected: int64(1000000000000),
},
{
typ: &btf.Int{
Size: 8,
},
value: "1000000000000",
expected: uint64(1000000000000),
},
{
typ: &btf.Int{
Size: 1,
},
value: "foo",
comment: "Value is not an int",
},
{
typ: &btf.Array{},
value: "1",
comment: "Type is not btf.Int",
},
{
typ: &btf.Int{
Size: 16,
},
value: "1",
comment: "Size is wrong",
},
{
typ: &btf.Typedef{
Type: &btf.Int{
Size: 1,
},
},
value: "1",
expected: uint8(1),
},
{
typ: &btf.Array{
Type: &btf.Int{
Size: 1,
Encoding: btf.Char,
},
Nelems: 6,
},
value: `"foobar"`,
expected: []byte("foobar"),
},
{
typ: &btf.Array{
Type: &btf.Int{
Size: 1,
Encoding: btf.Unsigned,
},
Nelems: 3,
},
value: `"foobar"`,
expected: []byte("foo"),
},
{
typ: &btf.Array{
Type: &btf.Int{
Size: 1,
Encoding: btf.Signed,
},
Nelems: 2,
},
value: `"42"`,
expected: []byte("42"),
},
{
typ: &btf.Int{},
value: `"foo"`,
comment: "Type is not btf.Array",
},
{
typ: &btf.Array{},
value: `"foo"`,
comment: "Type is not btf.Array of btf.Int",
},
{
typ: &btf.Array{
Type: &btf.Int{
Size: 1,
Encoding: btf.Bool,
},
},
comment: "Type is not btf.Array of btf.Int of size 1 which is not btf.Bool",
},
{
typ: &btf.Array{
Type: &btf.Int{
Size: 4,
Encoding: btf.Char,
},
},
value: `"foo"`,
comment: "Type is not btf.Array of btf.Char of size 1",
},
{
typ: &btf.Array{
Type: &btf.Int{
Size: 1,
Encoding: btf.Char,
},
},
value: `"foo`,
comment: `Value does not start and end with '"'`,
},
}
for _, c := range cases {
if len(c.comment) > 0 {
err := PutValue(make([]byte, 0), c.typ, c.value)
qt.Assert(t, err, qt.IsNotNil, qt.Commentf(c.comment))
continue
}
var buf bytes.Buffer
err := binary.Write(&buf, internal.NativeEndian, c.expected)
if err != nil {
t.Fatal(err)
}
expected := buf.Bytes()
data := make([]byte, len(expected))
err = PutValue(data, c.typ, c.value)
qt.Assert(t, err, qt.IsNil)
qt.Assert(t, data, qt.DeepEquals, expected)
}
}
@@ -0,0 +1,11 @@
CONFIG_TRISTATE=m
# CONFIG_IS_NOT_SET is not set
CONFIG_BOOL=y
CONFIG_CHAR=100
CONFIG_USHORT=30000
CONFIG_INT=123456
CONFIG_ULONG=0xDEADBEEFC0DE
CONFIG_STR="abracad"
CONFIG_FOO="foo"
CONFIG_FOO="bar"