1// Copyright 2015 CoreOS, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package unit
16
17import (
18	"bufio"
19	"bytes"
20	"errors"
21	"fmt"
22	"io"
23	"strings"
24	"unicode"
25)
26
27const (
28	// SYSTEMD_LINE_MAX mimics the maximum line length that systemd can use.
29	// On typical systemd platforms (i.e. modern Linux), this will most
30	// commonly be 2048, so let's use that as a sanity check.
31	// Technically, we should probably pull this at runtime:
32	//    SYSTEMD_LINE_MAX = int(C.sysconf(C.__SC_LINE_MAX))
33	// but this would introduce an (unfortunate) dependency on cgo
34	SYSTEMD_LINE_MAX = 2048
35
36	// SYSTEMD_NEWLINE defines characters that systemd considers indicators
37	// for a newline.
38	SYSTEMD_NEWLINE = "\r\n"
39)
40
41var (
42	// ErrLineTooLong gets returned when a line is too long for systemd to handle.
43	ErrLineTooLong = fmt.Errorf("line too long (max %d bytes)", SYSTEMD_LINE_MAX)
44)
45
46// Deserialize parses a systemd unit file into a list of UnitOption objects.
47func Deserialize(f io.Reader) (opts []*UnitOption, err error) {
48	lexer, optchan, errchan := newLexer(f)
49	go lexer.lex()
50
51	for opt := range optchan {
52		opts = append(opts, &(*opt))
53	}
54
55	err = <-errchan
56	return opts, err
57}
58
59func newLexer(f io.Reader) (*lexer, <-chan *UnitOption, <-chan error) {
60	optchan := make(chan *UnitOption)
61	errchan := make(chan error, 1)
62	buf := bufio.NewReader(f)
63
64	return &lexer{buf, optchan, errchan, ""}, optchan, errchan
65}
66
67type lexer struct {
68	buf     *bufio.Reader
69	optchan chan *UnitOption
70	errchan chan error
71	section string
72}
73
74func (l *lexer) lex() {
75	defer func() {
76		close(l.optchan)
77		close(l.errchan)
78	}()
79	next := l.lexNextSection
80	for next != nil {
81		if l.buf.Buffered() >= SYSTEMD_LINE_MAX {
82			// systemd truncates lines longer than LINE_MAX
83			// https://bugs.freedesktop.org/show_bug.cgi?id=85308
84			// Rather than allowing this to pass silently, let's
85			// explicitly gate people from encountering this
86			line, err := l.buf.Peek(SYSTEMD_LINE_MAX)
87			if err != nil {
88				l.errchan <- err
89				return
90			}
91			if !bytes.ContainsAny(line, SYSTEMD_NEWLINE) {
92				l.errchan <- ErrLineTooLong
93				return
94			}
95		}
96
97		var err error
98		next, err = next()
99		if err != nil {
100			l.errchan <- err
101			return
102		}
103	}
104}
105
106type lexStep func() (lexStep, error)
107
108func (l *lexer) lexSectionName() (lexStep, error) {
109	sec, err := l.buf.ReadBytes(']')
110	if err != nil {
111		return nil, errors.New("unable to find end of section")
112	}
113
114	return l.lexSectionSuffixFunc(string(sec[:len(sec)-1])), nil
115}
116
117func (l *lexer) lexSectionSuffixFunc(section string) lexStep {
118	return func() (lexStep, error) {
119		garbage, _, err := l.toEOL()
120		if err != nil {
121			return nil, err
122		}
123
124		garbage = bytes.TrimSpace(garbage)
125		if len(garbage) > 0 {
126			return nil, fmt.Errorf("found garbage after section name %s: %v", l.section, garbage)
127		}
128
129		return l.lexNextSectionOrOptionFunc(section), nil
130	}
131}
132
133func (l *lexer) ignoreLineFunc(next lexStep) lexStep {
134	return func() (lexStep, error) {
135		for {
136			line, _, err := l.toEOL()
137			if err != nil {
138				return nil, err
139			}
140
141			line = bytes.TrimSuffix(line, []byte{' '})
142
143			// lack of continuation means this line has been exhausted
144			if !bytes.HasSuffix(line, []byte{'\\'}) {
145				break
146			}
147		}
148
149		// reached end of buffer, safe to exit
150		return next, nil
151	}
152}
153
154func (l *lexer) lexNextSection() (lexStep, error) {
155	r, _, err := l.buf.ReadRune()
156	if err != nil {
157		if err == io.EOF {
158			err = nil
159		}
160		return nil, err
161	}
162
163	if r == '[' {
164		return l.lexSectionName, nil
165	} else if isComment(r) {
166		return l.ignoreLineFunc(l.lexNextSection), nil
167	}
168
169	return l.lexNextSection, nil
170}
171
172func (l *lexer) lexNextSectionOrOptionFunc(section string) lexStep {
173	return func() (lexStep, error) {
174		r, _, err := l.buf.ReadRune()
175		if err != nil {
176			if err == io.EOF {
177				err = nil
178			}
179			return nil, err
180		}
181
182		if unicode.IsSpace(r) {
183			return l.lexNextSectionOrOptionFunc(section), nil
184		} else if r == '[' {
185			return l.lexSectionName, nil
186		} else if isComment(r) {
187			return l.ignoreLineFunc(l.lexNextSectionOrOptionFunc(section)), nil
188		}
189
190		l.buf.UnreadRune()
191		return l.lexOptionNameFunc(section), nil
192	}
193}
194
195func (l *lexer) lexOptionNameFunc(section string) lexStep {
196	return func() (lexStep, error) {
197		var partial bytes.Buffer
198		for {
199			r, _, err := l.buf.ReadRune()
200			if err != nil {
201				return nil, err
202			}
203
204			if r == '\n' || r == '\r' {
205				return nil, errors.New("unexpected newline encountered while parsing option name")
206			}
207
208			if r == '=' {
209				break
210			}
211
212			partial.WriteRune(r)
213		}
214
215		name := strings.TrimSpace(partial.String())
216		return l.lexOptionValueFunc(section, name, bytes.Buffer{}), nil
217	}
218}
219
220func (l *lexer) lexOptionValueFunc(section, name string, partial bytes.Buffer) lexStep {
221	return func() (lexStep, error) {
222		for {
223			line, eof, err := l.toEOL()
224			if err != nil {
225				return nil, err
226			}
227
228			if len(bytes.TrimSpace(line)) == 0 {
229				break
230			}
231
232			partial.Write(line)
233
234			// lack of continuation means this value has been exhausted
235			idx := bytes.LastIndex(line, []byte{'\\'})
236			if idx == -1 || idx != (len(line)-1) {
237				break
238			}
239
240			if !eof {
241				partial.WriteRune('\n')
242			}
243
244			return l.lexOptionValueFunc(section, name, partial), nil
245		}
246
247		val := partial.String()
248		if strings.HasSuffix(val, "\n") {
249			// A newline was added to the end, so the file didn't end with a backslash.
250			// => Keep the newline
251			val = strings.TrimSpace(val) + "\n"
252		} else {
253			val = strings.TrimSpace(val)
254		}
255		l.optchan <- &UnitOption{Section: section, Name: name, Value: val}
256
257		return l.lexNextSectionOrOptionFunc(section), nil
258	}
259}
260
261// toEOL reads until the end-of-line or end-of-file.
262// Returns (data, EOFfound, error)
263func (l *lexer) toEOL() ([]byte, bool, error) {
264	line, err := l.buf.ReadBytes('\n')
265	// ignore EOF here since it's roughly equivalent to EOL
266	if err != nil && err != io.EOF {
267		return nil, false, err
268	}
269
270	line = bytes.TrimSuffix(line, []byte{'\r'})
271	line = bytes.TrimSuffix(line, []byte{'\n'})
272
273	return line, err == io.EOF, nil
274}
275
276func isComment(r rune) bool {
277	return r == '#' || r == ';'
278}
279