aboutsummaryrefslogtreecommitdiffstats
path: root/tools/syz-kconf/parser.go
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2020-10-18 18:36:16 +0200
committerDmitry Vyukov <dvyukov@google.com>2020-10-21 10:22:10 +0200
commit99b9ab211949d89a18fcb4f0f97341a4d4cbe1bb (patch)
treea7536c3b6a1f55aa8d79bd5b9fe110ba2b9e03fd /tools/syz-kconf/parser.go
parent7982be0f5b006a67168ab52cd80a5d5f89d60d89 (diff)
tools/syz-kconf: add tool
syz-kconf generates all Linux kernel configs. Update #2171
Diffstat (limited to 'tools/syz-kconf/parser.go')
-rw-r--r--tools/syz-kconf/parser.go283
1 files changed, 283 insertions, 0 deletions
diff --git a/tools/syz-kconf/parser.go b/tools/syz-kconf/parser.go
new file mode 100644
index 000000000..dad090b8a
--- /dev/null
+++ b/tools/syz-kconf/parser.go
@@ -0,0 +1,283 @@
+// Copyright 2020 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+
+ "github.com/google/syzkaller/pkg/kconfig"
+ "github.com/google/syzkaller/pkg/vcs"
+ "gopkg.in/yaml.v3"
+)
+
+type Instance struct {
+ Name string
+ Kernel Kernel
+ Verbatim []byte
+ Shell []Shell
+ Features Features
+ ConfigMap map[string]*Config
+ Configs []*Config
+}
+
+type Config struct {
+ Name string
+ Value string
+ Override bool
+ Optional bool
+ Constraints []string
+ File string
+ Line int
+}
+
+type Kernel struct {
+ Repo string
+ Tag string
+}
+
+type Shell struct {
+ Cmd string
+ Constraints []string
+}
+
+type Features map[string]bool
+
+func (features Features) Match(constraints []string) bool {
+ for _, feat := range constraints {
+ switch feat {
+ case featOptional, featOverride, featWeak:
+ continue
+ }
+ if feat[0] == '-' {
+ if features[feat[1:]] {
+ return false
+ }
+ } else if !features[feat] {
+ return false
+ }
+ }
+ return true
+}
+
+type rawMain struct {
+ Instances []map[string][]string
+ Includes []map[string][]string
+}
+
+type rawFile struct {
+ Kernel struct {
+ Repo string
+ Tag string
+ }
+ Shell []yaml.Node
+ Verbatim string
+ Config []yaml.Node
+}
+
+func parseMainSpec(file string) ([]*Instance, error) {
+ data, err := ioutil.ReadFile(file)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read config file: %v", err)
+ }
+ dec := yaml.NewDecoder(bytes.NewReader(data))
+ dec.KnownFields(true)
+ raw := new(rawMain)
+ if err := dec.Decode(raw); err != nil {
+ return nil, fmt.Errorf("failed to parse %v: %v", file, err)
+ }
+ var instances []*Instance
+ for _, inst := range raw.Instances {
+ for name, features := range inst {
+ inst, err := parseInstance(name, filepath.Dir(file), features, raw.Includes)
+ if err != nil {
+ return nil, err
+ }
+ instances = append(instances, inst)
+ inst, err = parseInstance(name+"-base", filepath.Dir(file), append(features, featBaseline), raw.Includes)
+ if err != nil {
+ return nil, err
+ }
+ instances = append(instances, inst)
+ }
+ }
+ return instances, nil
+}
+
+func parseInstance(name, configDir string, features []string, includes []map[string][]string) (*Instance, error) {
+ inst := &Instance{
+ Name: name,
+ Features: make(Features),
+ ConfigMap: make(map[string]*Config),
+ }
+ for _, feat := range features {
+ inst.Features[feat] = true
+ }
+ errs := new(Errors)
+ for _, include := range includes {
+ for file, features := range include {
+ if !inst.Features.Match(features) {
+ continue
+ }
+ raw, err := parseFile(filepath.Join(configDir, "bits", file))
+ if err != nil {
+ return nil, err
+ }
+ mergeFile(inst, raw, file, errs)
+ }
+ }
+ inst.Verbatim = bytes.TrimSpace(inst.Verbatim)
+ return inst, errs.err()
+}
+
+func parseFile(file string) (*rawFile, error) {
+ data, err := ioutil.ReadFile(file)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read %v: %v", file, err)
+ }
+ dec := yaml.NewDecoder(bytes.NewReader(data))
+ dec.KnownFields(true)
+ raw := new(rawFile)
+ if err := dec.Decode(raw); err != nil {
+ return nil, fmt.Errorf("failed to parse %v: %v", file, err)
+ }
+ return raw, nil
+}
+
+func mergeFile(inst *Instance, raw *rawFile, file string, errs *Errors) {
+ if raw.Kernel.Repo != "" || raw.Kernel.Tag != "" {
+ if !vcs.CheckRepoAddress(raw.Kernel.Repo) {
+ errs.push("%v: bad kernel repo %q", file, raw.Kernel.Repo)
+ }
+ if !vcs.CheckBranch(raw.Kernel.Tag) {
+ errs.push("%v: bad kernel tag %q", file, raw.Kernel.Tag)
+ }
+ if inst.Kernel.Repo != "" {
+ errs.push("%v: kernel is set twice", file)
+ }
+ inst.Kernel = raw.Kernel
+ }
+ for _, node := range raw.Shell {
+ cmd, _, constraints, err := parseNode(node)
+ if err != nil {
+ errs.push("%v:%v: %v", file, node.Line, err)
+ }
+ inst.Shell = append(inst.Shell, Shell{
+ Cmd: cmd,
+ Constraints: constraints,
+ })
+ }
+ inst.Verbatim = append(append(inst.Verbatim, raw.Verbatim...), '\n')
+ for _, node := range raw.Config {
+ mergeConfig(inst, file, node, errs)
+ }
+}
+
+func mergeConfig(inst *Instance, file string, node yaml.Node, errs *Errors) {
+ name, val, constraints, err := parseNode(node)
+ if err != nil {
+ errs.push("%v:%v: %v", file, node.Line, err)
+ return
+ }
+ cfg := &Config{
+ Name: name,
+ Value: val,
+ File: file,
+ Line: node.Line,
+ }
+ for _, feat := range constraints {
+ switch feat {
+ case featOverride:
+ cfg.Override = true
+ case featOptional:
+ cfg.Optional = true
+ case featWeak:
+ cfg.Override, cfg.Optional = true, true
+ default:
+ cfg.Constraints = append(cfg.Constraints, feat)
+ }
+ }
+ if prev := inst.ConfigMap[name]; prev != nil {
+ if !cfg.Override {
+ errs.push("%v:%v: %v is already defined at %v:%v", file, node.Line, name, prev.File, prev.Line)
+ }
+ *prev = *cfg
+ return
+ }
+ if cfg.Override && !cfg.Optional {
+ errs.push("%v:%v: %v nothing to override", file, node.Line, name)
+ }
+ inst.ConfigMap[name] = cfg
+ inst.Configs = append(inst.Configs, cfg)
+}
+
+func parseNode(node yaml.Node) (name, val string, constraints []string, err error) {
+ // Simplest case: - FOO.
+ val = kconfig.Yes
+ if node.Decode(&name) == nil {
+ return
+ }
+ complexVal := make(map[string]yaml.Node)
+ if err = node.Decode(complexVal); err != nil {
+ return
+ }
+ var valNode yaml.Node
+ for k, v := range complexVal {
+ name, valNode = k, v
+ break
+ }
+ // Case: - FOO: 42.
+ if intVal := 0; valNode.Decode(&intVal) == nil {
+ val = fmt.Sprint(intVal)
+ return
+ }
+ if valNode.Decode(&val) == nil {
+ // Case: - FOO: "string".
+ if valNode.Style == yaml.DoubleQuotedStyle {
+ val = `"` + val + `"`
+ return
+ }
+ // Case: - FOO: n.
+ if valNode.Style == 0 && val == "n" {
+ val = kconfig.No
+ return
+ }
+ err = fmt.Errorf("bad config format")
+ return
+ }
+ // Case: - FOO: [...].
+ propsNode := []yaml.Node{}
+ if err = valNode.Decode(&propsNode); err != nil {
+ return
+ }
+ for _, propNode := range propsNode {
+ prop := ""
+ if err = propNode.Decode(&prop); err != nil {
+ return
+ }
+ if propNode.Style == yaml.DoubleQuotedStyle {
+ val = `"` + prop + `"`
+ } else if prop == "n" {
+ val = kconfig.No
+ } else {
+ constraints = append(constraints, prop)
+ }
+ }
+ return
+}
+
+type Errors []byte
+
+func (errs *Errors) push(msg string, args ...interface{}) {
+ *errs = append(*errs, fmt.Sprintf(msg+"\n", args...)...)
+}
+
+func (errs *Errors) err() error {
+ if len(*errs) == 0 {
+ return nil
+ }
+ return fmt.Errorf("%s", *errs)
+}