github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/kconfig/kconfig.go (about) 1 // Copyright 2020 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 // Package kconfig implements parsing of the Linux kernel Kconfig and .config files 5 // and provides some algorithms to work with these files. For Kconfig reference see: 6 // https://www.kernel.org/doc/html/latest/kbuild/kconfig-language.html 7 package kconfig 8 9 import ( 10 "fmt" 11 "os" 12 "path/filepath" 13 "strings" 14 "sync" 15 16 "github.com/google/syzkaller/sys/targets" 17 ) 18 19 // KConfig represents a parsed Kconfig file (including includes). 20 type KConfig struct { 21 Root *Menu // mainmenu 22 Configs map[string]*Menu // only config/menuconfig entries 23 } 24 25 // Menu represents a single hierarchical menu or config. 26 type Menu struct { 27 Kind MenuKind // config/menu/choice/etc 28 Type ConfigType // tristate/bool/string/etc 29 Name string // name without CONFIG_ 30 Elems []*Menu // sub-elements for menus 31 Parent *Menu // parent menu, non-nil for everythign except for mainmenu 32 33 kconf *KConfig // back-link to the owning KConfig 34 prompts []prompt 35 defaults []defaultVal 36 dependsOn expr 37 visibleIf expr 38 deps map[string]bool 39 depsOnce sync.Once 40 } 41 42 type prompt struct { 43 text string 44 cond expr 45 } 46 47 type defaultVal struct { 48 val expr 49 cond expr 50 } 51 52 type ( 53 MenuKind int 54 ConfigType int 55 ) 56 57 const ( 58 _ MenuKind = iota 59 MenuConfig 60 MenuGroup 61 MenuChoice 62 MenuComment 63 ) 64 const ( 65 _ ConfigType = iota 66 TypeBool 67 TypeTristate 68 TypeString 69 TypeInt 70 TypeHex 71 ) 72 73 // DependsOn returns all transitive configs this config depends on. 74 func (m *Menu) DependsOn() map[string]bool { 75 m.depsOnce.Do(func() { 76 m.deps = make(map[string]bool) 77 if m.dependsOn != nil { 78 m.dependsOn.collectDeps(m.deps) 79 } 80 if m.visibleIf != nil { 81 m.visibleIf.collectDeps(m.deps) 82 } 83 var indirect []string 84 for cfg := range m.deps { 85 dep := m.kconf.Configs[cfg] 86 if dep == nil { 87 delete(m.deps, cfg) 88 continue 89 } 90 for cfg1 := range dep.DependsOn() { 91 indirect = append(indirect, cfg1) 92 } 93 } 94 for _, cfg := range indirect { 95 m.deps[cfg] = true 96 } 97 }) 98 return m.deps 99 } 100 101 func (m *Menu) Prompt() string { 102 // TODO: check prompt conditions, some prompts may be not visible. 103 // If all prompts are not visible, then then menu if effectively disabled (at least for user). 104 for _, p := range m.prompts { 105 return p.text 106 } 107 return "" 108 } 109 110 type kconfigParser struct { 111 *parser 112 target *targets.Target 113 includes []*parser 114 stack []*Menu 115 cur *Menu 116 baseDir string 117 helpIdent int 118 } 119 120 func Parse(target *targets.Target, file string) (*KConfig, error) { 121 data, err := os.ReadFile(file) 122 if err != nil { 123 return nil, fmt.Errorf("failed to open Kconfig file %v: %w", file, err) 124 } 125 return ParseData(target, data, file) 126 } 127 128 func ParseData(target *targets.Target, data []byte, file string) (*KConfig, error) { 129 kp := &kconfigParser{ 130 parser: newParser(data, file), 131 target: target, 132 baseDir: filepath.Dir(file), 133 } 134 kp.parseFile() 135 if kp.err != nil { 136 return nil, kp.err 137 } 138 if len(kp.stack) == 0 { 139 return nil, fmt.Errorf("no mainmenu in config") 140 } 141 root := kp.stack[0] 142 kconf := &KConfig{ 143 Root: root, 144 Configs: make(map[string]*Menu), 145 } 146 kconf.walk(root, nil, nil) 147 return kconf, nil 148 } 149 150 func (kconf *KConfig) walk(m *Menu, dependsOn, visibleIf expr) { 151 m.kconf = kconf 152 m.dependsOn = exprAnd(dependsOn, m.dependsOn) 153 m.visibleIf = exprAnd(visibleIf, m.visibleIf) 154 if m.Kind == MenuConfig { 155 kconf.Configs[m.Name] = m 156 } 157 for _, elem := range m.Elems { 158 kconf.walk(elem, m.dependsOn, m.visibleIf) 159 } 160 } 161 162 func (kp *kconfigParser) parseFile() { 163 for kp.nextLine() { 164 kp.parseLine() 165 if kp.TryConsume("#") { 166 _ = kp.ConsumeLine() 167 } 168 } 169 kp.endCurrent() 170 } 171 172 func (kp *kconfigParser) parseLine() { 173 if kp.eol() { 174 return 175 } 176 if kp.helpIdent != 0 { 177 if kp.identLevel() >= kp.helpIdent { 178 _ = kp.ConsumeLine() 179 return 180 } 181 kp.helpIdent = 0 182 } 183 if kp.TryConsume("#") { 184 _ = kp.ConsumeLine() 185 return 186 } 187 if kp.TryConsume("$") { 188 _ = kp.Shell() 189 return 190 } 191 ident := kp.Ident() 192 if kp.TryConsume("=") || kp.TryConsume(":=") { 193 // Macro definition, see: 194 // https://www.kernel.org/doc/html/latest/kbuild/kconfig-macro-language.html 195 // We don't use this for anything now. 196 kp.ConsumeLine() 197 return 198 } 199 kp.parseMenu(ident) 200 } 201 202 func (kp *kconfigParser) parseMenu(cmd string) { 203 switch cmd { 204 case "source": 205 file, ok := kp.TryQuotedString() 206 if !ok { 207 file = kp.ConsumeLine() 208 } 209 kp.includeSource(file) 210 case "mainmenu": 211 kp.pushCurrent(&Menu{ 212 Kind: MenuConfig, 213 prompts: []prompt{{text: kp.QuotedString()}}, 214 }) 215 case "comment": 216 kp.newCurrent(&Menu{ 217 Kind: MenuComment, 218 prompts: []prompt{{text: kp.QuotedString()}}, 219 }) 220 case "menu": 221 kp.pushCurrent(&Menu{ 222 Kind: MenuGroup, 223 prompts: []prompt{{text: kp.QuotedString()}}, 224 }) 225 case "if": 226 kp.pushCurrent(&Menu{ 227 Kind: MenuGroup, 228 visibleIf: kp.parseExpr(), 229 }) 230 case "choice": 231 kp.pushCurrent(&Menu{ 232 Kind: MenuChoice, 233 }) 234 case "endmenu", "endif", "endchoice": 235 kp.popCurrent() 236 case "config", "menuconfig": 237 kp.newCurrent(&Menu{ 238 Kind: MenuConfig, 239 Name: kp.Ident(), 240 }) 241 default: 242 kp.parseConfigType(cmd) 243 } 244 } 245 246 func (kp *kconfigParser) parseConfigType(typ string) { 247 cur := kp.current() 248 switch typ { 249 case "tristate": 250 cur.Type = TypeTristate 251 kp.tryParsePrompt() 252 case "def_tristate": 253 cur.Type = TypeTristate 254 kp.parseDefaultValue() 255 case "bool": 256 cur.Type = TypeBool 257 kp.tryParsePrompt() 258 case "def_bool": 259 cur.Type = TypeBool 260 kp.parseDefaultValue() 261 case "int": 262 cur.Type = TypeInt 263 kp.tryParsePrompt() 264 case "def_int": 265 cur.Type = TypeInt 266 kp.parseDefaultValue() 267 case "hex": 268 cur.Type = TypeHex 269 kp.tryParsePrompt() 270 case "def_hex": 271 cur.Type = TypeHex 272 kp.parseDefaultValue() 273 case "string": 274 cur.Type = TypeString 275 kp.tryParsePrompt() 276 case "def_string": 277 cur.Type = TypeString 278 kp.parseDefaultValue() 279 default: 280 kp.parseProperty(typ) 281 } 282 } 283 284 func (kp *kconfigParser) parseProperty(prop string) { 285 cur := kp.current() 286 switch prop { 287 case "prompt": 288 kp.tryParsePrompt() 289 case "depends": 290 kp.MustConsume("on") 291 cur.dependsOn = exprAnd(cur.dependsOn, kp.parseExpr()) 292 case "visible": 293 kp.MustConsume("if") 294 cur.visibleIf = exprAnd(cur.visibleIf, kp.parseExpr()) 295 case "select", "imply": 296 _ = kp.Ident() 297 if kp.TryConsume("if") { 298 _ = kp.parseExpr() 299 } 300 case "option": 301 // It can be 'option foo', or 'option bar="BAZ"'. 302 kp.ConsumeLine() 303 case "modules": 304 case "optional": 305 case "default": 306 kp.parseDefaultValue() 307 case "range": 308 _, _ = kp.parseExpr(), kp.parseExpr() // from, to 309 if kp.TryConsume("if") { 310 _ = kp.parseExpr() 311 } 312 case "help", "---help---": 313 // Help rules are tricky: end of help is identified by smaller indentation level 314 // as would be rendered on a terminal with 8-column tabs setup, minus empty lines. 315 for kp.nextLine() { 316 if kp.eol() { 317 continue 318 } 319 kp.helpIdent = kp.identLevel() 320 kp.ConsumeLine() 321 break 322 } 323 default: 324 kp.failf("unknown line") 325 } 326 } 327 328 func (kp *kconfigParser) includeSource(file string) { 329 kp.newCurrent(nil) 330 file = kp.expandString(file) 331 file = filepath.Join(kp.baseDir, file) 332 data, err := os.ReadFile(file) 333 if err != nil { 334 kp.failf("%v", err) 335 return 336 } 337 kp.includes = append(kp.includes, kp.parser) 338 kp.parser = newParser(data, file) 339 kp.parseFile() 340 err = kp.err 341 kp.parser = kp.includes[len(kp.includes)-1] 342 kp.includes = kp.includes[:len(kp.includes)-1] 343 if kp.err == nil { 344 kp.err = err 345 } 346 } 347 348 func (kp *kconfigParser) pushCurrent(m *Menu) { 349 kp.endCurrent() 350 kp.cur = m 351 kp.stack = append(kp.stack, m) 352 } 353 354 func (kp *kconfigParser) popCurrent() { 355 kp.endCurrent() 356 if len(kp.stack) < 2 { 357 kp.failf("unbalanced endmenu") 358 return 359 } 360 last := kp.stack[len(kp.stack)-1] 361 kp.stack = kp.stack[:len(kp.stack)-1] 362 top := kp.stack[len(kp.stack)-1] 363 last.Parent = top 364 top.Elems = append(top.Elems, last) 365 } 366 367 func (kp *kconfigParser) newCurrent(m *Menu) { 368 kp.endCurrent() 369 kp.cur = m 370 } 371 372 func (kp *kconfigParser) current() *Menu { 373 if kp.cur == nil { 374 kp.failf("config property outside of config") 375 return &Menu{} 376 } 377 return kp.cur 378 } 379 380 func (kp *kconfigParser) endCurrent() { 381 if kp.cur == nil { 382 return 383 } 384 if len(kp.stack) == 0 { 385 kp.failf("unbalanced endmenu") 386 return 387 } 388 top := kp.stack[len(kp.stack)-1] 389 if top != kp.cur { 390 kp.cur.Parent = top 391 top.Elems = append(top.Elems, kp.cur) 392 } 393 kp.cur = nil 394 } 395 396 func (kp *kconfigParser) tryParsePrompt() { 397 if str, ok := kp.TryQuotedString(); ok { 398 prompt := prompt{ 399 text: str, 400 } 401 if kp.TryConsume("if") { 402 prompt.cond = kp.parseExpr() 403 } 404 kp.current().prompts = append(kp.current().prompts, prompt) 405 } 406 } 407 408 func (kp *kconfigParser) parseDefaultValue() { 409 def := defaultVal{val: kp.parseExpr()} 410 if kp.TryConsume("if") { 411 def.cond = kp.parseExpr() 412 } 413 kp.current().defaults = append(kp.current().defaults, def) 414 } 415 416 func (kp *kconfigParser) expandString(str string) string { 417 str = strings.Replace(str, "$(SRCARCH)", kp.target.KernelHeaderArch, -1) 418 str = strings.Replace(str, "$SRCARCH", kp.target.KernelHeaderArch, -1) 419 str = strings.Replace(str, "$(KCONFIG_EXT_PREFIX)", "", -1) 420 str = strings.Replace(str, "$(MALI_KCONFIG_EXT_PREFIX)", "", -1) // ChromeOS. 421 return str 422 }