1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
package drivers
import (
"bufio"
"fmt"
"maps"
"strconv"
"strings"
"github.com/lxc/incus/v6/internal/server/instance/drivers/cfg"
)
// sectionContent represents the content of a section, without its name.
type sectionContent struct {
comment string
entries map[string]string
}
// section represents a section pointing to its content.
type section struct {
name string
content *sectionContent
}
// qemuRawCfgOverride generates a new QEMU configuration from an original one and an override entry.
func qemuRawCfgOverride(conf []cfg.Section, confOverride string) ([]cfg.Section, error) {
// We define a data structure optimized for lookup and insertion…
indexedSections := map[string]map[int]*sectionContent{}
// … and another one keeping insertion order. This saves us a few cycles at the expense of a few
// bytes of RAM.
orderedSections := []section{}
// We first iterate over the original config to populate both our data structures.
for _, sec := range conf {
indexedSection, ok := indexedSections[sec.Name]
if !ok {
indexedSection = map[int]*sectionContent{}
indexedSections[sec.Name] = indexedSection
}
// We perform a copy of the map to avoid modifying the original one
entries := make(map[string]string, len(sec.Entries))
maps.Copy(entries, sec.Entries)
content := §ionContent{
comment: sec.Comment,
entries: entries,
}
indexedSection[len(indexedSection)] = content
orderedSections = append(orderedSections, section{
name: sec.Name,
content: content,
})
}
var currentIndexedSection map[int]*sectionContent
var currentIndex int
var currentSectionName string
// We set the changed flag to true for our first iteration.
changed := true
scanner := bufio.NewScanner(strings.NewReader(confOverride))
// Then, we parse the override string.
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
length := len(line)
if length == 0 {
continue
}
if strings.HasPrefix(line, "[") {
// Find closing `]` for section name.
end := strings.IndexByte(line, ']')
if end <= 1 {
return nil, fmt.Errorf("Invalid section header (must be a section name enclosed in square brackets): %q", line)
}
// If a section is defined in the override but has no key, remove its entries.
if !changed {
(*currentIndexedSection[currentIndex]).entries = make(map[string]string)
}
changed = false
currentSectionName = strings.TrimSpace(line[1:end])
currentIndex = 0
if length > end+1 {
// Optional section index
rest := line[end+1:]
e := fmt.Errorf("Invalid section index (must be an integer enclosed in square brackets): %q", rest)
if !strings.HasPrefix(rest, "[") || !strings.HasSuffix(rest, "]") {
return nil, e
}
var err error
currentIndex, err = strconv.Atoi(rest[1 : len(rest)-1])
if err != nil {
return nil, e
}
}
var ok bool
currentIndexedSection, ok = indexedSections[currentSectionName]
if !ok {
// If there is no section with this name, we are creating a new section.
currentIndexedSection = map[int]*sectionContent{}
indexedSections[currentSectionName] = currentIndexedSection
}
_, ok = currentIndexedSection[currentIndex]
if !ok {
// If there is no section with this index, we are creating a new section.
emptyContent := §ionContent{entries: make(map[string]string)}
currentIndexedSection[currentIndex] = emptyContent
indexedSections[currentSectionName] = currentIndexedSection
orderedSections = append(orderedSections, section{
name: currentSectionName,
content: emptyContent,
})
}
} else {
if currentSectionName == "" {
return nil, fmt.Errorf("Expected section header, got: %q", line)
}
eqLoc := strings.IndexByte(line, '=')
if eqLoc < 1 {
return nil, fmt.Errorf("Invalid property override line (must be `key=value`): %q", line)
}
key := strings.TrimSpace(line[:eqLoc])
value := strings.TrimSpace(line[eqLoc+1:])
if strings.HasPrefix(value, "\"") {
// We are dealing with a quoted value.
var err error
value, err = strconv.Unquote(value)
if err != nil {
return nil, fmt.Errorf("Invalid quoted value: %q", value)
}
}
changed = true
if value == "" {
// If the value associated to this key is empty, delete the key.
delete((*currentIndexedSection[currentIndex]).entries, key)
} else {
(*currentIndexedSection[currentIndex]).entries[key] = value
}
}
}
// Same as above, if a section is defined in the override but has no key, remove its entries.
if !changed {
(*currentIndexedSection[currentIndex]).entries = make(map[string]string)
}
res := []cfg.Section{}
for _, orderedSection := range orderedSections {
if len((*orderedSection.content).entries) > 0 {
res = append(res, cfg.Section{
Name: orderedSection.name,
Comment: (*orderedSection.content).comment,
Entries: (*orderedSection.content).entries,
})
}
}
return res, nil
}
|