mirror of
https://github.com/vbatts/go-mtree.git
synced 2024-11-14 20:58:41 +00:00
63dc31a80a
As per the spec[1], Full entries must not affect the current directory. Handling this incorrectly caused us issues with certain manifests (ones with mixed Relative and Full entries, which is something casync does by accident). This is a partial fix for the issues with verifying casync-mtree's output but there are a few other issues to iron out (including one within casync). [1]: https://man.netbsd.org/mtree.5 Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
108 lines
2.4 KiB
Go
108 lines
2.4 KiB
Go
package mtree
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ParseSpec reads a stream of an mtree specification, and returns the DirectoryHierarchy
|
|
func ParseSpec(r io.Reader) (*DirectoryHierarchy, error) {
|
|
s := bufio.NewScanner(r)
|
|
i := int(0)
|
|
creator := dhCreator{
|
|
DH: &DirectoryHierarchy{},
|
|
}
|
|
for s.Scan() {
|
|
str := s.Text()
|
|
trimmedStr := strings.TrimLeftFunc(str, func(c rune) bool {
|
|
return c == ' ' || c == '\t'
|
|
})
|
|
e := Entry{Pos: i}
|
|
switch {
|
|
case strings.HasPrefix(trimmedStr, "#"):
|
|
e.Raw = str
|
|
if strings.HasPrefix(trimmedStr, "#mtree") {
|
|
e.Type = SignatureType
|
|
} else {
|
|
e.Type = CommentType
|
|
// from here, the comment could be "# key: value" metadata
|
|
// or a relative path hint
|
|
}
|
|
case str == "":
|
|
e.Type = BlankType
|
|
// nothing else to do here
|
|
case strings.HasPrefix(str, "/"):
|
|
e.Type = SpecialType
|
|
// collapse any escaped newlines
|
|
for {
|
|
if strings.HasSuffix(str, `\`) {
|
|
str = str[:len(str)-1]
|
|
s.Scan()
|
|
str += s.Text()
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
// parse the options
|
|
f := strings.Fields(str)
|
|
e.Name = f[0]
|
|
e.Keywords = StringToKeyVals(f[1:])
|
|
if e.Name == "/set" {
|
|
creator.curSet = &e
|
|
} else if e.Name == "/unset" {
|
|
creator.curSet = nil
|
|
}
|
|
case len(strings.Fields(str)) > 0 && strings.Fields(str)[0] == "..":
|
|
e.Type = DotDotType
|
|
e.Raw = str
|
|
if creator.curDir != nil {
|
|
creator.curDir = creator.curDir.Parent
|
|
}
|
|
// nothing else to do here
|
|
case len(strings.Fields(str)) > 0:
|
|
// collapse any escaped newlines
|
|
for {
|
|
if strings.HasSuffix(str, `\`) {
|
|
str = str[:len(str)-1]
|
|
s.Scan()
|
|
str += s.Text()
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
// parse the options
|
|
f := strings.Fields(str)
|
|
e.Name = filepath.Clean(f[0])
|
|
e.Keywords = StringToKeyVals(f[1:])
|
|
// TODO: gather keywords if using tar stream
|
|
var isDir bool
|
|
for _, kv := range e.Keywords {
|
|
if kv.Keyword() == "type" {
|
|
isDir = kv.Value() == "dir"
|
|
}
|
|
}
|
|
if strings.Contains(e.Name, "/") {
|
|
e.Type = FullType
|
|
} else {
|
|
e.Type = RelativeType
|
|
e.Parent = creator.curDir
|
|
if isDir {
|
|
creator.curDir = &e
|
|
}
|
|
}
|
|
if !isDir {
|
|
creator.curEnt = &e
|
|
}
|
|
e.Set = creator.curSet
|
|
default:
|
|
// TODO(vbatts) log a warning?
|
|
continue
|
|
}
|
|
creator.DH.Entries = append(creator.DH.Entries, e)
|
|
i++
|
|
}
|
|
return creator.DH, s.Err()
|
|
}
|