From ecfa7f2fb3674f8ecd4a693a6b164e1d3c6541d6 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 15 Oct 2020 11:37:25 +0200 Subject: pkg/kconfig: add .config parsing Update #2171 --- pkg/kconfig/config.go | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 pkg/kconfig/config.go (limited to 'pkg/kconfig/config.go') 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$`) +) -- cgit mrf-deployment