blob: 0f3f09dc265c61b383b01a6f1457ecbe31cd6a88 [file] [log] [blame]
// 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$`)
)