1
0
mirror of https://github.com/golang/go synced 2024-11-25 20:07:56 -07:00

cmd/go/internal/fsys: refactor overlay consistency checks

Do the overlay consistency checks separate from constructing
the overlay data structure. This makes sure that the data structure
can be changed without worrying about losing the checks.

Change-Id: I9ff50cc366b5362adc5570f94e6caf646ddf5046
Reviewed-on: https://go-review.googlesource.com/c/go/+/628700
Auto-Submit: Russ Cox <rsc@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Matloob <matloob@golang.org>
This commit is contained in:
Russ Cox 2024-11-16 16:49:00 -05:00 committed by Gopher Robot
parent 8767a090d5
commit 8f22369136
2 changed files with 76 additions and 65 deletions

View File

@ -88,6 +88,11 @@ type overlayJSON struct {
Replace map[string]string
}
type replace struct {
from string
to string
}
type node struct {
actual string // empty if a directory
children map[string]*node // path element → file or directory
@ -230,78 +235,69 @@ func initFromJSON(js []byte) error {
return fmt.Errorf("parsing overlay JSON: %v", err)
}
// Canonicalize the paths in the overlay map.
// Use reverseCanonicalized to check for collisions:
// no two 'from' paths should abs to the same path.
overlay = make(map[string]*node)
reverseCanonicalized := make(map[string]string) // inverse of abs operation, to check for duplicates
// Build a table of file and directory nodes from the replacement map.
seen := make(map[string]string)
var list []replace
for _, from := range slices.Sorted(maps.Keys(ojs.Replace)) {
to := ojs.Replace[from]
// Canonicalize paths and check for a collision.
if from == "" {
return fmt.Errorf("empty string key in overlay map")
}
cfrom := abs(from)
to = abs(to)
if otherFrom, seen := reverseCanonicalized[cfrom]; seen {
return fmt.Errorf(
"duplicate paths %s and %s in overlay map", otherFrom, from)
afrom := abs(from)
if old, ok := seen[afrom]; ok {
return fmt.Errorf("duplicate paths %s and %s in overlay map", old, from)
}
reverseCanonicalized[cfrom] = from
from = cfrom
seen[afrom] = from
list = append(list, replace{from: afrom, to: ojs.Replace[from]})
}
// Create node for overlaid file.
dir, base := filepath.Dir(from), filepath.Base(from)
if n, ok := overlay[from]; ok {
// All 'from' paths in the overlay are file paths. Since the from paths
// are in a map, they are unique, so if the node already exists we added
// it below when we create parent directory nodes. That is, that
// both a file and a path to one of its parent directories exist as keys
// in the Replace map.
//
// This only applies if the overlay directory has any files or directories
// in it: placeholder directories that only contain deleted files don't
// count. They are safe to be overwritten with actual files.
for fname, f := range n.children {
if !f.isDeleted() {
return fmt.Errorf("inconsistent files %s and %s in overlay map", filepath.Join(from, fname), from)
}
}
slices.SortFunc(list, func(x, y replace) int { return cmp(x.from, y.from) })
for i, r := range list {
if r.to == "" { // deleted
continue
}
overlay[from] = &node{actual: to}
// Add parent directory nodes to overlay structure.
childNode := overlay[from]
for {
dirNode := overlay[dir]
if dirNode == nil || dirNode.isDeleted() {
dirNode = &node{children: make(map[string]*node)}
overlay[dir] = dirNode
}
if childNode.isDeleted() {
// Only create one parent for a deleted file:
// the directory only conditionally exists if
// there are any non-deleted children, so
// we don't create their parents.
if dirNode.isDir() {
dirNode.children[base] = childNode
}
// have file for r.from; look for child file implying r.from is a directory
prefix := r.from + string(filepath.Separator)
for _, next := range list[i+1:] {
if !strings.HasPrefix(next.from, prefix) {
break
}
if !dirNode.isDir() {
// This path already exists as a file, so it can't be a parent
// directory. See comment at error above.
return fmt.Errorf("inconsistent files %s and %s in overlay map", dir, from)
if next.to != "" {
// found child file
return fmt.Errorf("inconsistent files %s and %s in overlay map", r.from, next.from)
}
dirNode.children[base] = childNode
parent := filepath.Dir(dir)
if parent == dir {
break // reached the top; there is no parent
}
}
overlay = make(map[string]*node)
for _, r := range list {
n := &node{actual: abs(r.to)}
from := r.from
overlay[from] = n
for {
dir, base := filepath.Dir(from), filepath.Base(from)
if dir == from {
break
}
dir, base = parent, filepath.Base(dir)
childNode = dirNode
dn := overlay[dir]
if dn == nil || dn.isDeleted() {
dn = &node{children: make(map[string]*node)}
overlay[dir] = dn
}
if n.isDeleted() && !dn.isDir() {
break
}
if !dn.isDir() {
panic("fsys inconsistency")
}
dn.children[base] = n
if n.isDeleted() {
// Deletion is recorded now.
// Don't need to create entire parent chain,
// because we don't need to force parents to exist.
break
}
from, n = dir, dn
}
}
@ -591,3 +587,20 @@ func (f fakeDir) Sys() any { return nil }
func (f fakeDir) String() string {
return fs.FormatFileInfo(f)
}
func cmp(x, y string) int {
for i := 0; i < len(x) && i < len(y); i++ {
xi := int(x[i])
yi := int(y[i])
if xi == filepath.Separator {
xi = -1
}
if yi == filepath.Separator {
yi = -1
}
if xi != yi {
return xi - yi
}
}
return len(x) - len(y)
}

View File

@ -1146,12 +1146,10 @@ var badOverlayTests = []struct {
{`{"Replace": {"/tmp/x": "y", "x": "y"}}`,
`duplicate paths /tmp/x and x in overlay map`},
{`{"Replace": {"/tmp/x/z": "z", "x":"y"}}`,
`inconsistent files /tmp/x/z and /tmp/x in overlay map`},
`inconsistent files /tmp/x and /tmp/x/z in overlay map`},
{`{"Replace": {"/tmp/x/z/z2": "z", "x":"y"}}`,
// TODO: Error should say /tmp/x/z/z2
`inconsistent files /tmp/x/z and /tmp/x in overlay map`},
`inconsistent files /tmp/x and /tmp/x/z/z2 in overlay map`},
{`{"Replace": {"/tmp/x": "y", "x/z/z2": "z"}}`,
// TODO: Error should say /tmp/x/z/z2
`inconsistent files /tmp/x and /tmp/x/z/z2 in overlay map`},
}