github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/internal/kconfig/kconfig.go (about)

     1  package kconfig
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"compress/gzip"
     7  	"fmt"
     8  	"io"
     9  	"math"
    10  	"os"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/cilium/ebpf/btf"
    15  	"github.com/cilium/ebpf/internal"
    16  )
    17  
    18  // Find find a kconfig file on the host.
    19  // It first reads from /boot/config- of the current running kernel and tries
    20  // /proc/config.gz if nothing was found in /boot.
    21  // If none of the file provide a kconfig, it returns an error.
    22  func Find() (*os.File, error) {
    23  	kernelRelease, err := internal.KernelRelease()
    24  	if err != nil {
    25  		return nil, fmt.Errorf("cannot get kernel release: %w", err)
    26  	}
    27  
    28  	path := "/boot/config-" + kernelRelease
    29  	f, err := os.Open(path)
    30  	if err == nil {
    31  		return f, nil
    32  	}
    33  
    34  	f, err = os.Open("/proc/config.gz")
    35  	if err == nil {
    36  		return f, nil
    37  	}
    38  
    39  	return nil, fmt.Errorf("neither %s nor /proc/config.gz provide a kconfig", path)
    40  }
    41  
    42  // Parse parses the kconfig file for which a reader is given.
    43  // All the CONFIG_* which are in filter and which are set set will be
    44  // put in the returned map as key with their corresponding value as map value.
    45  // If filter is nil, no filtering will occur.
    46  // If the kconfig file is not valid, error will be returned.
    47  func Parse(source io.ReaderAt, filter map[string]struct{}) (map[string]string, error) {
    48  	var r io.Reader
    49  	zr, err := gzip.NewReader(io.NewSectionReader(source, 0, math.MaxInt64))
    50  	if err != nil {
    51  		r = io.NewSectionReader(source, 0, math.MaxInt64)
    52  	} else {
    53  		// Source is gzip compressed, transparently decompress.
    54  		r = zr
    55  	}
    56  
    57  	ret := make(map[string]string, len(filter))
    58  
    59  	s := bufio.NewScanner(r)
    60  
    61  	for s.Scan() {
    62  		line := s.Bytes()
    63  		err = processKconfigLine(line, ret, filter)
    64  		if err != nil {
    65  			return nil, fmt.Errorf("cannot parse line: %w", err)
    66  		}
    67  
    68  		if filter != nil && len(ret) == len(filter) {
    69  			break
    70  		}
    71  	}
    72  
    73  	if err := s.Err(); err != nil {
    74  		return nil, fmt.Errorf("cannot parse: %w", err)
    75  	}
    76  
    77  	if zr != nil {
    78  		return ret, zr.Close()
    79  	}
    80  
    81  	return ret, nil
    82  }
    83  
    84  // Golang translation of libbpf bpf_object__process_kconfig_line():
    85  // https://github.com/libbpf/libbpf/blob/fbd60dbff51c870f5e80a17c4f2fd639eb80af90/src/libbpf.c#L1874
    86  // It does the same checks but does not put the data inside the BPF map.
    87  func processKconfigLine(line []byte, m map[string]string, filter map[string]struct{}) error {
    88  	// Ignore empty lines and "# CONFIG_* is not set".
    89  	if !bytes.HasPrefix(line, []byte("CONFIG_")) {
    90  		return nil
    91  	}
    92  
    93  	key, value, found := bytes.Cut(line, []byte{'='})
    94  	if !found {
    95  		return fmt.Errorf("line %q does not contain separator '='", line)
    96  	}
    97  
    98  	if len(value) == 0 {
    99  		return fmt.Errorf("line %q has no value", line)
   100  	}
   101  
   102  	if filter != nil {
   103  		// NB: map[string(key)] gets special optimisation help from the compiler
   104  		// and doesn't allocate. Don't turn this into a variable.
   105  		_, ok := filter[string(key)]
   106  		if !ok {
   107  			return nil
   108  		}
   109  	}
   110  
   111  	// This can seem odd, but libbpf only sets the value the first time the key is
   112  	// met:
   113  	// https://github.com/torvalds/linux/blob/0d85b27b0cc6/tools/lib/bpf/libbpf.c#L1906-L1908
   114  	_, ok := m[string(key)]
   115  	if !ok {
   116  		m[string(key)] = string(value)
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  // PutValue translates the value given as parameter depending on the BTF
   123  // type, the translated value is then written to the byte array.
   124  func PutValue(data []byte, typ btf.Type, value string) error {
   125  	typ = btf.UnderlyingType(typ)
   126  
   127  	switch value {
   128  	case "y", "n", "m":
   129  		return putValueTri(data, typ, value)
   130  	default:
   131  		if strings.HasPrefix(value, `"`) {
   132  			return putValueString(data, typ, value)
   133  		}
   134  		return putValueNumber(data, typ, value)
   135  	}
   136  }
   137  
   138  // Golang translation of libbpf_tristate enum:
   139  // https://github.com/libbpf/libbpf/blob/fbd60dbff51c870f5e80a17c4f2fd639eb80af90/src/bpf_helpers.h#L169
   140  type triState int
   141  
   142  const (
   143  	TriNo     triState = 0
   144  	TriYes    triState = 1
   145  	TriModule triState = 2
   146  )
   147  
   148  func putValueTri(data []byte, typ btf.Type, value string) error {
   149  	switch v := typ.(type) {
   150  	case *btf.Int:
   151  		if v.Encoding != btf.Bool {
   152  			return fmt.Errorf("cannot add tri value, expected btf.Bool, got: %v", v.Encoding)
   153  		}
   154  
   155  		if v.Size != 1 {
   156  			return fmt.Errorf("cannot add tri value, expected size of 1 byte, got: %d", v.Size)
   157  		}
   158  
   159  		switch value {
   160  		case "y":
   161  			data[0] = 1
   162  		case "n":
   163  			data[0] = 0
   164  		default:
   165  			return fmt.Errorf("cannot use %q for btf.Bool", value)
   166  		}
   167  	case *btf.Enum:
   168  		if v.Name != "libbpf_tristate" {
   169  			return fmt.Errorf("cannot use enum %q, only libbpf_tristate is supported", v.Name)
   170  		}
   171  
   172  		var tri triState
   173  		switch value {
   174  		case "y":
   175  			tri = TriYes
   176  		case "m":
   177  			tri = TriModule
   178  		case "n":
   179  			tri = TriNo
   180  		default:
   181  			return fmt.Errorf("value %q is not support for libbpf_tristate", value)
   182  		}
   183  
   184  		internal.NativeEndian.PutUint64(data, uint64(tri))
   185  	default:
   186  		return fmt.Errorf("cannot add number value, expected btf.Int or btf.Enum, got: %T", v)
   187  	}
   188  
   189  	return nil
   190  }
   191  
   192  func putValueString(data []byte, typ btf.Type, value string) error {
   193  	array, ok := typ.(*btf.Array)
   194  	if !ok {
   195  		return fmt.Errorf("cannot add string value, expected btf.Array, got %T", array)
   196  	}
   197  
   198  	contentType, ok := btf.UnderlyingType(array.Type).(*btf.Int)
   199  	if !ok {
   200  		return fmt.Errorf("cannot add string value, expected array of btf.Int, got %T", contentType)
   201  	}
   202  
   203  	// Any Int, which is not bool, of one byte could be used to store char:
   204  	// https://github.com/torvalds/linux/blob/1a5304fecee5/tools/lib/bpf/libbpf.c#L3637-L3638
   205  	if contentType.Size != 1 && contentType.Encoding != btf.Bool {
   206  		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)
   207  	}
   208  
   209  	if !strings.HasPrefix(value, `"`) || !strings.HasSuffix(value, `"`) {
   210  		return fmt.Errorf(`value %q must start and finish with '"'`, value)
   211  	}
   212  
   213  	str := strings.Trim(value, `"`)
   214  
   215  	// We need to trim string if the bpf array is smaller.
   216  	if uint32(len(str)) >= array.Nelems {
   217  		str = str[:array.Nelems]
   218  	}
   219  
   220  	// Write the string content to .kconfig.
   221  	copy(data, str)
   222  
   223  	return nil
   224  }
   225  
   226  func putValueNumber(data []byte, typ btf.Type, value string) error {
   227  	integer, ok := typ.(*btf.Int)
   228  	if !ok {
   229  		return fmt.Errorf("cannot add number value, expected *btf.Int, got: %T", integer)
   230  	}
   231  
   232  	size := integer.Size
   233  	sizeInBits := size * 8
   234  
   235  	var n uint64
   236  	var err error
   237  	if integer.Encoding == btf.Signed {
   238  		parsed, e := strconv.ParseInt(value, 0, int(sizeInBits))
   239  
   240  		n = uint64(parsed)
   241  		err = e
   242  	} else {
   243  		parsed, e := strconv.ParseUint(value, 0, int(sizeInBits))
   244  
   245  		n = uint64(parsed)
   246  		err = e
   247  	}
   248  
   249  	if err != nil {
   250  		return fmt.Errorf("cannot parse value: %w", err)
   251  	}
   252  
   253  	return PutInteger(data, integer, n)
   254  }
   255  
   256  // PutInteger writes n into data.
   257  //
   258  // integer determines how much is written into data and what the valid values
   259  // are.
   260  func PutInteger(data []byte, integer *btf.Int, n uint64) error {
   261  	// This function should match set_kcfg_value_num in libbpf.
   262  	if integer.Encoding == btf.Bool && n > 1 {
   263  		return fmt.Errorf("invalid boolean value: %d", n)
   264  	}
   265  
   266  	if len(data) < int(integer.Size) {
   267  		return fmt.Errorf("can't fit an integer of size %d into a byte slice of length %d", integer.Size, len(data))
   268  	}
   269  
   270  	switch integer.Size {
   271  	case 1:
   272  		if integer.Encoding == btf.Signed && (int64(n) > math.MaxInt8 || int64(n) < math.MinInt8) {
   273  			return fmt.Errorf("can't represent %d as a signed integer of size %d", int64(n), integer.Size)
   274  		}
   275  		data[0] = byte(n)
   276  	case 2:
   277  		if integer.Encoding == btf.Signed && (int64(n) > math.MaxInt16 || int64(n) < math.MinInt16) {
   278  			return fmt.Errorf("can't represent %d as a signed integer of size %d", int64(n), integer.Size)
   279  		}
   280  		internal.NativeEndian.PutUint16(data, uint16(n))
   281  	case 4:
   282  		if integer.Encoding == btf.Signed && (int64(n) > math.MaxInt32 || int64(n) < math.MinInt32) {
   283  			return fmt.Errorf("can't represent %d as a signed integer of size %d", int64(n), integer.Size)
   284  		}
   285  		internal.NativeEndian.PutUint32(data, uint32(n))
   286  	case 8:
   287  		internal.NativeEndian.PutUint64(data, uint64(n))
   288  	default:
   289  		return fmt.Errorf("size (%d) is not valid, expected: 1, 2, 4 or 8", integer.Size)
   290  	}
   291  
   292  	return nil
   293  }