aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/kconfig/config.go
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2020-10-15 11:37:25 +0200
committerDmitry Vyukov <dvyukov@google.com>2020-10-21 10:22:10 +0200
commitecfa7f2fb3674f8ecd4a693a6b164e1d3c6541d6 (patch)
tree1dc18a08d41883c1f3b18563973d4788a0897e4b /pkg/kconfig/config.go
parentf0ba6809a8b86c889e26579ef4a7a2cba244e12f (diff)
pkg/kconfig: add .config parsing
Update #2171
Diffstat (limited to 'pkg/kconfig/config.go')
-rw-r--r--pkg/kconfig/config.go153
1 files changed, 153 insertions, 0 deletions
diff --git a/pkg/kconfig/config.go b/pkg/kconfig/config.go
new file mode 100644
index 000000000..70bdb1b17
--- /dev/null
+++ b/pkg/kconfig/config.go
@@ -0,0 +1,153 @@
+// 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 kconfig
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "regexp"
+)
+
+// ConfigFile represents a parsed .config file.
+// It should not be modified directly, only by means of calling methods.
+// The only exception is Config.Value which may be modified directly.
+// Note: config names don't include CONFIG_ prefix, here and in other public interfaces,
+// users of this package should never mention CONFIG_.
+// Use Yes/Mod/No consts to check for/set config to particular values.
+type ConfigFile struct {
+ Configs []*Config
+ Map map[string]*Config // duplicates Configs for convenience
+ comments []string
+}
+
+type Config struct {
+ Name string
+ Value string
+ comments []string
+}
+
+const (
+ Yes = "y"
+ Mod = "m"
+ No = "---===[[[is not set]]]===---" // to make it more obvious when some code writes it directly
+ prefix = "CONFIG_"
+)
+
+// Value returns config value, or No if it's not present at all.
+func (cf *ConfigFile) Value(name string) string {
+ cfg := cf.Map[name]
+ if cfg == nil {
+ return No
+ }
+ return cfg.Value
+}
+
+// Set changes config value, or adds it if it's not yet present.
+func (cf *ConfigFile) Set(name, val string) {
+ cfg := cf.Map[name]
+ if cfg == nil {
+ cfg = &Config{
+ Name: name,
+ Value: val,
+ }
+ cf.Map[name] = cfg
+ cf.Configs = append(cf.Configs, cfg)
+ }
+ cfg.Value = val
+ cfg.comments = append(cfg.comments, cf.comments...)
+ cf.comments = nil
+}
+
+// Unset sets config value to No, if it's present in the config.
+func (cf *ConfigFile) Unset(name string) {
+ cfg := cf.Map[name]
+ if cfg == nil {
+ return
+ }
+ cfg.Value = No
+}
+
+func (cf *ConfigFile) ModToYes() {
+ for _, cfg := range cf.Configs {
+ if cfg.Value == Mod {
+ cfg.Value = Yes
+ }
+ }
+}
+
+func (cf *ConfigFile) ModToNo() {
+ for _, cfg := range cf.Configs {
+ if cfg.Value == Mod {
+ cfg.Value = No
+ }
+ }
+}
+
+func (cf *ConfigFile) Serialize() []byte {
+ buf := new(bytes.Buffer)
+ for _, cfg := range cf.Configs {
+ for _, comment := range cfg.comments {
+ fmt.Fprintf(buf, "%v\n", comment)
+ }
+ if cfg.Value == No {
+ fmt.Fprintf(buf, "# %v%v is not set\n", prefix, cfg.Name)
+ } else {
+ fmt.Fprintf(buf, "%v%v=%v\n", prefix, cfg.Name, cfg.Value)
+ }
+ }
+ for _, comment := range cf.comments {
+ fmt.Fprintf(buf, "%v\n", comment)
+ }
+ return buf.Bytes()
+}
+
+func ParseConfig(file string) (*ConfigFile, error) {
+ data, err := ioutil.ReadFile(file)
+ if err != nil {
+ return nil, fmt.Errorf("failed to open .config file %v: %v", file, err)
+ }
+ return ParseConfigData(data, file)
+}
+
+func ParseConfigData(data []byte, file string) (*ConfigFile, error) {
+ cf := &ConfigFile{
+ Map: make(map[string]*Config),
+ }
+ s := bufio.NewScanner(bytes.NewReader(data))
+ for s.Scan() {
+ cf.parseLine(s.Text())
+ }
+ return cf, nil
+}
+
+func (cf *ConfigFile) clone() *ConfigFile {
+ cf1 := &ConfigFile{
+ Map: make(map[string]*Config),
+ comments: cf.comments,
+ }
+ for _, cfg := range cf.Configs {
+ cfg1 := new(Config)
+ *cfg1 = *cfg
+ cf1.Configs = append(cf1.Configs, cfg1)
+ cf1.Map[cfg1.Name] = cfg1
+ }
+ return cf1
+}
+
+func (cf *ConfigFile) parseLine(text string) {
+ if match := reConfigY.FindStringSubmatch(text); match != nil {
+ cf.Set(match[1], match[2])
+ } else if match := reConfigN.FindStringSubmatch(text); match != nil {
+ cf.Set(match[1], No)
+ } else {
+ cf.comments = append(cf.comments, text)
+ }
+}
+
+var (
+ reConfigY = regexp.MustCompile(`^` + prefix + `([A-Za-z0-9_]+)=(y|m|(?:-?[0-9]+)|(?:0x[0-9a-fA-F]+)|(?:".*?"))$`)
+ reConfigN = regexp.MustCompile(`^# ` + prefix + `([A-Za-z0-9_]+) is not set$`)
+)