diff options
Diffstat (limited to 'vendor/github.com/coreos/go-systemd/unit/deserialize.go')
-rw-r--r-- | vendor/github.com/coreos/go-systemd/unit/deserialize.go | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/vendor/github.com/coreos/go-systemd/unit/deserialize.go b/vendor/github.com/coreos/go-systemd/unit/deserialize.go new file mode 100644 index 00000000..8a88162f --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/unit/deserialize.go @@ -0,0 +1,276 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unit + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "strings" + "unicode" +) + +const ( + // SYSTEMD_LINE_MAX mimics the maximum line length that systemd can use. + // On typical systemd platforms (i.e. modern Linux), this will most + // commonly be 2048, so let's use that as a sanity check. + // Technically, we should probably pull this at runtime: + // SYSTEMD_LINE_MAX = int(C.sysconf(C.__SC_LINE_MAX)) + // but this would introduce an (unfortunate) dependency on cgo + SYSTEMD_LINE_MAX = 2048 + + // characters that systemd considers indicate a newline + SYSTEMD_NEWLINE = "\r\n" +) + +var ( + ErrLineTooLong = fmt.Errorf("line too long (max %d bytes)", SYSTEMD_LINE_MAX) +) + +// Deserialize parses a systemd unit file into a list of UnitOption objects. +func Deserialize(f io.Reader) (opts []*UnitOption, err error) { + lexer, optchan, errchan := newLexer(f) + go lexer.lex() + + for opt := range optchan { + opts = append(opts, &(*opt)) + } + + err = <-errchan + return opts, err +} + +func newLexer(f io.Reader) (*lexer, <-chan *UnitOption, <-chan error) { + optchan := make(chan *UnitOption) + errchan := make(chan error, 1) + buf := bufio.NewReader(f) + + return &lexer{buf, optchan, errchan, ""}, optchan, errchan +} + +type lexer struct { + buf *bufio.Reader + optchan chan *UnitOption + errchan chan error + section string +} + +func (l *lexer) lex() { + var err error + defer func() { + close(l.optchan) + close(l.errchan) + }() + next := l.lexNextSection + for next != nil { + if l.buf.Buffered() >= SYSTEMD_LINE_MAX { + // systemd truncates lines longer than LINE_MAX + // https://bugs.freedesktop.org/show_bug.cgi?id=85308 + // Rather than allowing this to pass silently, let's + // explicitly gate people from encountering this + line, err := l.buf.Peek(SYSTEMD_LINE_MAX) + if err != nil { + l.errchan <- err + return + } + if bytes.IndexAny(line, SYSTEMD_NEWLINE) == -1 { + l.errchan <- ErrLineTooLong + return + } + } + + next, err = next() + if err != nil { + l.errchan <- err + return + } + } +} + +type lexStep func() (lexStep, error) + +func (l *lexer) lexSectionName() (lexStep, error) { + sec, err := l.buf.ReadBytes(']') + if err != nil { + return nil, errors.New("unable to find end of section") + } + + return l.lexSectionSuffixFunc(string(sec[:len(sec)-1])), nil +} + +func (l *lexer) lexSectionSuffixFunc(section string) lexStep { + return func() (lexStep, error) { + garbage, _, err := l.toEOL() + if err != nil { + return nil, err + } + + garbage = bytes.TrimSpace(garbage) + if len(garbage) > 0 { + return nil, fmt.Errorf("found garbage after section name %s: %v", l.section, garbage) + } + + return l.lexNextSectionOrOptionFunc(section), nil + } +} + +func (l *lexer) ignoreLineFunc(next lexStep) lexStep { + return func() (lexStep, error) { + for { + line, _, err := l.toEOL() + if err != nil { + return nil, err + } + + line = bytes.TrimSuffix(line, []byte{' '}) + + // lack of continuation means this line has been exhausted + if !bytes.HasSuffix(line, []byte{'\\'}) { + break + } + } + + // reached end of buffer, safe to exit + return next, nil + } +} + +func (l *lexer) lexNextSection() (lexStep, error) { + r, _, err := l.buf.ReadRune() + if err != nil { + if err == io.EOF { + err = nil + } + return nil, err + } + + if r == '[' { + return l.lexSectionName, nil + } else if isComment(r) { + return l.ignoreLineFunc(l.lexNextSection), nil + } + + return l.lexNextSection, nil +} + +func (l *lexer) lexNextSectionOrOptionFunc(section string) lexStep { + return func() (lexStep, error) { + r, _, err := l.buf.ReadRune() + if err != nil { + if err == io.EOF { + err = nil + } + return nil, err + } + + if unicode.IsSpace(r) { + return l.lexNextSectionOrOptionFunc(section), nil + } else if r == '[' { + return l.lexSectionName, nil + } else if isComment(r) { + return l.ignoreLineFunc(l.lexNextSectionOrOptionFunc(section)), nil + } + + l.buf.UnreadRune() + return l.lexOptionNameFunc(section), nil + } +} + +func (l *lexer) lexOptionNameFunc(section string) lexStep { + return func() (lexStep, error) { + var partial bytes.Buffer + for { + r, _, err := l.buf.ReadRune() + if err != nil { + return nil, err + } + + if r == '\n' || r == '\r' { + return nil, errors.New("unexpected newline encountered while parsing option name") + } + + if r == '=' { + break + } + + partial.WriteRune(r) + } + + name := strings.TrimSpace(partial.String()) + return l.lexOptionValueFunc(section, name, bytes.Buffer{}), nil + } +} + +func (l *lexer) lexOptionValueFunc(section, name string, partial bytes.Buffer) lexStep { + return func() (lexStep, error) { + for { + line, eof, err := l.toEOL() + if err != nil { + return nil, err + } + + if len(bytes.TrimSpace(line)) == 0 { + break + } + + partial.Write(line) + + // lack of continuation means this value has been exhausted + idx := bytes.LastIndex(line, []byte{'\\'}) + if idx == -1 || idx != (len(line)-1) { + break + } + + if !eof { + partial.WriteRune('\n') + } + + return l.lexOptionValueFunc(section, name, partial), nil + } + + val := partial.String() + if strings.HasSuffix(val, "\n") { + // A newline was added to the end, so the file didn't end with a backslash. + // => Keep the newline + val = strings.TrimSpace(val) + "\n" + } else { + val = strings.TrimSpace(val) + } + l.optchan <- &UnitOption{Section: section, Name: name, Value: val} + + return l.lexNextSectionOrOptionFunc(section), nil + } +} + +// toEOL reads until the end-of-line or end-of-file. +// Returns (data, EOFfound, error) +func (l *lexer) toEOL() ([]byte, bool, error) { + line, err := l.buf.ReadBytes('\n') + // ignore EOF here since it's roughly equivalent to EOL + if err != nil && err != io.EOF { + return nil, false, err + } + + line = bytes.TrimSuffix(line, []byte{'\r'}) + line = bytes.TrimSuffix(line, []byte{'\n'}) + + return line, err == io.EOF, nil +} + +func isComment(r rune) bool { + return r == '#' || r == ';' +} |