// 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" "os" "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 := os.ReadFile(file) if err != nil { return nil, fmt.Errorf("failed to open .config file %v: %w", 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$`) )