-
Notifications
You must be signed in to change notification settings - Fork 0
/
kconfig.go
280 lines (232 loc) · 7.06 KB
/
kconfig.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
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)
}
return PutInteger(data, integer, n)
}
// PutInteger writes n into data.
//
// integer determines how much is written into data and what the valid values
// are.
func PutInteger(data []byte, integer *btf.Int, n uint64) error {
// This function should match set_kcfg_value_num in libbpf.
if integer.Encoding == btf.Bool && n > 1 {
return fmt.Errorf("invalid boolean value: %d", n)
}
switch integer.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", integer.Size)
}
return nil
}