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 }