github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/tools/syz-kconf/parser.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 main
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/google/syzkaller/pkg/kconfig"
    15  	"github.com/google/syzkaller/pkg/vcs"
    16  	"gopkg.in/yaml.v3"
    17  )
    18  
    19  type Instance struct {
    20  	Name      string
    21  	Kernel    Kernel
    22  	Compiler  string
    23  	Linker    string
    24  	Verbatim  []byte
    25  	Shell     []Shell
    26  	Features  Features
    27  	ConfigMap map[string]*Config
    28  	Configs   []*Config
    29  }
    30  
    31  type Config struct {
    32  	Name        string
    33  	Value       string
    34  	Optional    bool
    35  	Constraints []string
    36  	File        string
    37  	Line        int
    38  }
    39  
    40  type Kernel struct {
    41  	Repo string
    42  	Tag  string
    43  }
    44  
    45  type Shell struct {
    46  	Cmd         string
    47  	Constraints []string
    48  }
    49  
    50  type Features map[string]bool
    51  
    52  func (features Features) Match(constraints []string) bool {
    53  	for _, feat := range constraints {
    54  		if feat[0] == '-' {
    55  			if features[feat[1:]] {
    56  				return false
    57  			}
    58  		} else if !features[feat] {
    59  			return false
    60  		}
    61  	}
    62  	return true
    63  }
    64  
    65  func constraintsInclude(constraints []string, what string) bool {
    66  	for _, feat := range constraints {
    67  		if feat == what {
    68  			return true
    69  		}
    70  	}
    71  	return false
    72  }
    73  
    74  type rawMain struct {
    75  	Instances []map[string][]string
    76  	Includes  []map[string][]string
    77  }
    78  
    79  type rawFile struct {
    80  	Kernel struct {
    81  		Repo string
    82  		Tag  string
    83  	}
    84  	Compiler string
    85  	Linker   string
    86  	Shell    []yaml.Node
    87  	Verbatim string
    88  	Config   []yaml.Node
    89  }
    90  
    91  func parseMainSpec(file string) ([]*Instance, []string, error) {
    92  	data, err := os.ReadFile(file)
    93  	if err != nil {
    94  		return nil, nil, fmt.Errorf("failed to read config file: %w", err)
    95  	}
    96  	dec := yaml.NewDecoder(bytes.NewReader(data))
    97  	dec.KnownFields(true)
    98  	raw := new(rawMain)
    99  	if err := dec.Decode(raw); err != nil {
   100  		return nil, nil, fmt.Errorf("failed to parse %v: %w", file, err)
   101  	}
   102  	var unusedFeatures []string
   103  	var instances []*Instance
   104  	for _, inst := range raw.Instances {
   105  		for name, features := range inst {
   106  			if name == "_" {
   107  				unusedFeatures = features
   108  				continue
   109  			}
   110  			inst, err := parseInstance(name, filepath.Dir(file), features, raw.Includes)
   111  			if err != nil {
   112  				return nil, nil, fmt.Errorf("%v: %w", name, err)
   113  			}
   114  			instances = append(instances, inst)
   115  			inst, err = parseInstance(name+"-base", filepath.Dir(file),
   116  				append(features, featBaseline, featBaseConfig), raw.Includes)
   117  			if err != nil {
   118  				return nil, nil, err
   119  			}
   120  			instances = append(instances, inst)
   121  		}
   122  	}
   123  	return instances, unusedFeatures, nil
   124  }
   125  
   126  func parseInstance(name, configDir string, features []string, includes []map[string][]string) (*Instance, error) {
   127  	inst := &Instance{
   128  		Name:      name,
   129  		Features:  make(Features),
   130  		ConfigMap: make(map[string]*Config),
   131  	}
   132  	for _, feat := range features {
   133  		inst.Features[feat] = true
   134  	}
   135  	errs := new(Errors)
   136  	for _, include := range includes {
   137  		for file, features := range include {
   138  			raw, err := parseFile(filepath.Join(configDir, "bits", file))
   139  			if err != nil {
   140  				return nil, err
   141  			}
   142  			if inst.Features.Match(features) {
   143  				mergeFile(inst, raw, file, errs)
   144  			} else if inst.Features[featReduced] && constraintsInclude(features, "-"+featReduced) {
   145  				// For fragments that we exclude because of "reduced" config,
   146  				// we want to disable all configs listed there.
   147  				// For example, if the fragment enables config FOO, and we the defconfig
   148  				// also enabled FOO, we want to disable FOO to get reduced config.
   149  				for _, node := range raw.Config {
   150  					mergeConfig(inst, file, node, true, errs)
   151  				}
   152  			}
   153  		}
   154  	}
   155  	inst.Verbatim = bytes.TrimSpace(inst.Verbatim)
   156  	return inst, errs.err()
   157  }
   158  
   159  func parseFile(file string) (*rawFile, error) {
   160  	data, err := os.ReadFile(file)
   161  	if err != nil {
   162  		return nil, fmt.Errorf("failed to read %v: %w", file, err)
   163  	}
   164  	dec := yaml.NewDecoder(bytes.NewReader(data))
   165  	dec.KnownFields(true)
   166  	raw := new(rawFile)
   167  	if err := dec.Decode(raw); err != nil {
   168  		return nil, fmt.Errorf("failed to parse %v: %w", file, err)
   169  	}
   170  	return raw, nil
   171  }
   172  
   173  func mergeFile(inst *Instance, raw *rawFile, file string, errs *Errors) {
   174  	if raw.Kernel.Repo != "" || raw.Kernel.Tag != "" {
   175  		if !vcs.CheckRepoAddress(raw.Kernel.Repo) {
   176  			errs.push("%v: bad kernel repo %q", file, raw.Kernel.Repo)
   177  		}
   178  		if !vcs.CheckBranch(raw.Kernel.Tag) {
   179  			errs.push("%v: bad kernel tag %q", file, raw.Kernel.Tag)
   180  		}
   181  		if inst.Kernel.Repo != "" {
   182  			errs.push("%v: kernel is set twice", file)
   183  		}
   184  		inst.Kernel = raw.Kernel
   185  	}
   186  	if raw.Compiler != "" {
   187  		if inst.Compiler != "" {
   188  			errs.push("%v: compiler is set twice", file)
   189  		}
   190  		inst.Compiler = raw.Compiler
   191  	}
   192  	if raw.Linker != "" {
   193  		if inst.Linker != "" {
   194  			errs.push("%v: linker is set twice", file)
   195  		}
   196  		inst.Linker = raw.Linker
   197  	}
   198  	prependShell := []Shell{}
   199  	for _, node := range raw.Shell {
   200  		cmd, _, constraints, err := parseNode(node)
   201  		if err != nil {
   202  			errs.push("%v:%v: %v", file, node.Line, err)
   203  		}
   204  		prependShell = append(prependShell, Shell{
   205  			Cmd:         cmd,
   206  			Constraints: constraints,
   207  		})
   208  	}
   209  	inst.Shell = append(prependShell, inst.Shell...)
   210  	if raw.Verbatim != "" {
   211  		inst.Verbatim = append(append(inst.Verbatim, strings.TrimSpace(raw.Verbatim)...), '\n')
   212  	}
   213  	for _, node := range raw.Config {
   214  		mergeConfig(inst, file, node, false, errs)
   215  	}
   216  }
   217  
   218  func mergeConfig(inst *Instance, file string, node yaml.Node, reduced bool, errs *Errors) {
   219  	name, val, constraints, err := parseNode(node)
   220  	if err != nil {
   221  		errs.push("%v:%v: %v", file, node.Line, err)
   222  		return
   223  	}
   224  	if reduced {
   225  		if val != kconfig.No && val != kconfig.Yes {
   226  			return
   227  		}
   228  		val = kconfig.No
   229  		constraints = append(constraints, featWeak)
   230  	}
   231  	cfg := &Config{
   232  		Name:  name,
   233  		Value: val,
   234  		File:  file,
   235  		Line:  node.Line,
   236  	}
   237  	override, appendVal := false, false
   238  	for _, feat := range constraints {
   239  		switch feat {
   240  		case featOverride:
   241  			override = true
   242  		case featOptional:
   243  			cfg.Optional = true
   244  		case featWeak:
   245  			override, cfg.Optional = true, true
   246  		case featAppend:
   247  			override, appendVal = true, true
   248  		default:
   249  			cfg.Constraints = append(cfg.Constraints, feat)
   250  		}
   251  	}
   252  	if prev := inst.ConfigMap[name]; prev != nil {
   253  		if !override {
   254  			errs.push("%v:%v: %v is already defined at %v:%v", file, node.Line, name, prev.File, prev.Line)
   255  		}
   256  		if appendVal {
   257  			a, b := prev.Value, cfg.Value
   258  			if a == "" || a[len(a)-1] != '"' || b == "" || b[0] != '"' {
   259  				errs.push("%v:%v: bad values to append, want non-empty strings", file, node.Line)
   260  				return
   261  			}
   262  			prev.Value = a[:len(a)-1] + " " + b[1:]
   263  		} else {
   264  			*prev = *cfg
   265  		}
   266  		return
   267  	}
   268  	if override && !cfg.Optional {
   269  		errs.push("%v:%v: %v nothing to override", file, node.Line, name)
   270  	}
   271  	inst.ConfigMap[name] = cfg
   272  	inst.Configs = append(inst.Configs, cfg)
   273  }
   274  
   275  func parseNode(node yaml.Node) (name, val string, constraints []string, err error) {
   276  	// Simplest case: - FOO.
   277  	val = kconfig.Yes
   278  	if node.Decode(&name) == nil {
   279  		return
   280  	}
   281  	complexVal := make(map[string]yaml.Node)
   282  	if err = node.Decode(complexVal); err != nil {
   283  		return
   284  	}
   285  	var valNode yaml.Node
   286  	for k, v := range complexVal {
   287  		name, valNode = k, v
   288  		break
   289  	}
   290  	// Case: - FOO: 42.
   291  	if intVal := 0; valNode.Decode(&intVal) == nil {
   292  		val = fmt.Sprint(intVal)
   293  		return
   294  	}
   295  	if valNode.Decode(&val) == nil {
   296  		// Case: - FOO: "string".
   297  		if valNode.Style == yaml.DoubleQuotedStyle {
   298  			val = `"` + val + `"`
   299  			return
   300  		}
   301  		// Case: - FOO: n.
   302  		if valNode.Style == 0 && val == "n" {
   303  			val = kconfig.No
   304  			return
   305  		}
   306  		err = fmt.Errorf("bad config format")
   307  		return
   308  	}
   309  	// Case: - FOO: [...].
   310  	propsNode := []yaml.Node{}
   311  	if err = valNode.Decode(&propsNode); err != nil {
   312  		return
   313  	}
   314  	for _, propNode := range propsNode {
   315  		prop := ""
   316  		if err = propNode.Decode(&prop); err != nil {
   317  			return
   318  		}
   319  		if propNode.Style == yaml.DoubleQuotedStyle {
   320  			val = `"` + prop + `"`
   321  		} else if prop == "n" {
   322  			val = kconfig.No
   323  		} else if intVal, err := strconv.ParseUint(prop, 0, 64); err == nil {
   324  			val = fmt.Sprint(intVal)
   325  		} else {
   326  			constraints = append(constraints, prop)
   327  		}
   328  	}
   329  	return
   330  }
   331  
   332  type Errors []byte
   333  
   334  func (errs *Errors) push(msg string, args ...interface{}) {
   335  	*errs = append(*errs, fmt.Sprintf(msg+"\n", args...)...)
   336  }
   337  
   338  func (errs *Errors) err() error {
   339  	if len(*errs) == 0 {
   340  		return nil
   341  	}
   342  	return fmt.Errorf("%s", *errs)
   343  }