undocker/rootfs/tree.go

121 lines
2.5 KiB
Go
Raw Normal View History

2021-05-24 00:11:58 +03:00
package rootfs
2021-05-24 00:11:58 +03:00
import (
"path/filepath"
"sort"
"strings"
)
2021-05-24 00:11:58 +03:00
// tree is a way to store directory paths for whiteouts.
2021-05-24 00:11:58 +03:00
// It is semi-optimized for reads and non-optimized for writes;
// See Merge() and HasPrefix for trade-offs.
2021-05-24 00:11:58 +03:00
type tree struct {
2021-05-24 00:11:58 +03:00
name string
2021-05-24 00:11:58 +03:00
children []*tree
2021-05-24 00:11:58 +03:00
end bool
}
// newTree creates a new tree from a given path.
2021-05-24 00:11:58 +03:00
func newTree(paths ...string) *tree {
t := &tree{name: ".", children: []*tree{}}
2021-05-24 00:11:58 +03:00
for _, path := range paths {
t.Add(path)
}
return t
}
// Add adds a sequence to a tree
2021-05-24 00:11:58 +03:00
func (t *tree) Add(path string) {
2021-05-24 00:11:58 +03:00
t.add(strings.Split(filepath.Clean(path), "/"))
}
// HasPrefix returns if tree contains a prefix matching a given sequence.
// Search algorithm is naive: it does linear search when going through the
2021-05-24 00:11:58 +03:00
// nodes, whereas binary search would work here too. Since we expect number of
// children to be really small (usually 1 or 2), it does not really matter. If
// you find a real-world container with 30+ whiteout paths on a single path, it
// may make sense to replace the algorithm.
2021-05-24 00:11:58 +03:00
func (t *tree) HasPrefix(path string) bool {
2021-05-24 00:11:58 +03:00
return t.hasprefix(strings.Split(filepath.Clean(path), "/"))
}
// Merge merges adds t2 to t. It is not optimized for speed, since it's walking
// full branch for every other branch.
2021-05-24 00:11:58 +03:00
func (t *tree) Merge(t2 *tree) {
2021-05-24 00:11:58 +03:00
t.merge(t2, []string{})
}
// String stringifies a tree
2021-05-24 00:11:58 +03:00
func (t *tree) String() string {
2021-05-24 00:11:58 +03:00
if len(t.children) == 0 {
return "<empty>"
}
res := &stringer{[]string{}}
res.stringify(t, []string{})
sort.Strings(res.res)
return strings.Join(res.res, ":")
}
2021-05-24 00:11:58 +03:00
func (t *tree) add(nodes []string) {
2021-05-24 00:11:58 +03:00
if len(nodes) == 0 {
t.end = true
return
}
for i := range t.children {
if t.children[i].name == nodes[0] {
t.children[i].add(nodes[1:])
return
}
}
2021-05-24 00:11:58 +03:00
newNode := &tree{name: nodes[0]}
2021-05-24 00:11:58 +03:00
t.children = append(t.children, newNode)
newNode.add(nodes[1:])
}
2021-05-24 00:11:58 +03:00
func (t *tree) hasprefix(nodes []string) bool {
2021-05-24 00:11:58 +03:00
if len(nodes) == 0 {
return t.end
}
if t.end {
return true
}
for i := range t.children {
if t.children[i].name == nodes[0] {
return t.children[i].hasprefix(nodes[1:])
}
}
return false
}
type stringer struct {
res []string
}
2021-05-24 00:11:58 +03:00
func (s *stringer) stringify(t *tree, acc []string) {
2021-05-24 00:11:58 +03:00
if t.name == "" {
return
}
acc = append(acc, t.name)
if t.end {
s.res = append(s.res, strings.Join(acc, "/"))
}
for _, child := range t.children {
s.stringify(child, acc)
}
}
2021-05-24 00:11:58 +03:00
func (t *tree) merge(t2 *tree, acc []string) {
2021-05-24 00:11:58 +03:00
if t2.end {
t.add(append(acc[1:], t2.name))
}
acc = append(acc, t2.name)
for _, child := range t2.children {
t.merge(child, acc)
}
}