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 }