forked from coreos/fleet
/
file.go
131 lines (110 loc) · 3.26 KB
/
file.go
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
package unit
import (
"bytes"
"fmt"
"strings"
)
// Description returns the first Description option found in the [Unit] section.
// If the option is not defined, an empty string is returned.
func (u *Unit) Description() string {
if values := u.Contents["Unit"]["Description"]; len(values) > 0 {
return values[0]
}
return ""
}
func NewUnit(raw string) *Unit {
parsed := deserializeUnitFile(raw)
return &Unit{parsed, raw}
}
// NewUnitFromLegacyContents creates a Unit object from an obsolete unit
// file datastructure. This should only be used to remain backwards-compatible where necessary.
func NewUnitFromLegacyContents(contents map[string]map[string]string) *Unit {
var serialized string
for section, keyMap := range contents {
serialized += fmt.Sprintf("[%s]\n", section)
for key, value := range keyMap {
serialized += fmt.Sprintf("%s=%s\n", key, value)
}
serialized += "\n"
}
return NewUnit(serialized)
}
// deserializeUnitFile parses a systemd unit file and attempts to map its various sections and values.
// Currently this function is dangerously simple and should be rewritten to match the systemd unit file spec
func deserializeUnitFile(raw string) map[string]map[string][]string {
sections := make(map[string]map[string][]string)
var section string
var prev string
for _, line := range strings.Split(raw, "\n") {
// Join lines ending in backslash
if strings.HasSuffix(line, "\\") {
// Replace trailing slash with space
prev = prev + line[:len(line)-1] + " "
continue
}
// Concatenate any previous conjoined lines
if prev != "" {
line = prev + line
prev = ""
} else if strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
// Ignore commented-out lines that are not part of a continuation
continue
}
line = strings.TrimSpace(line)
// Ignore blank lines
if len(line) == 0 {
continue
}
// Check for section
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
section = line[1 : len(line)-1]
sections[section] = make(map[string][]string)
continue
}
// ignore any lines that aren't within a section
if len(section) == 0 {
continue
}
key, values := deserializeUnitLine(line)
for _, v := range values {
sections[section][key] = append(sections[section][key], v)
}
}
return sections
}
func deserializeUnitLine(line string) (key string, values []string) {
parts := strings.SplitN(line, "=", 2)
key = strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
if strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) {
for _, v := range parseMultivalueLine(value) {
values = append(values, v)
}
} else {
values = append(values, value)
}
return
}
// parseMultivalueLine parses a line that includes several quoted values separated by whitespaces.
// Example: MachineMetadata="foo=bar" "baz=qux"
func parseMultivalueLine(line string) (values []string) {
var v bytes.Buffer
w := false // check whether we're within quotes or not
for _, e := range []byte(line) {
// ignore quotes
if e == '"' {
w = !w
continue
}
if e == ' ' {
if !w { // between quoted values, keep the previous value and reset.
values = append(values, v.String())
v.Reset()
continue
}
}
v.WriteByte(e)
}
values = append(values, v.String())
return
}