Handle the case of multiple path component symlinks (including last component) in wildcard prefix

Signed-off-by: Aaron Lehmann <alehmann@netflix.com>
master
Aaron Lehmann 2021-09-07 16:46:00 -07:00
parent ddd18de18e
commit 98f54ff22c
2 changed files with 70 additions and 48 deletions

View File

@ -517,50 +517,14 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
iter = root.Iterator() iter = root.Iterator()
if opts.Wildcard { if opts.Wildcard {
// For consistency with what the copy implementation in fsutil origPrefix, k, kOk, err = wildcardPrefix(root, p)
// does: split pattern into non-wildcard prefix and rest of if err != nil {
// pattern, then follow symlinks when resolving the non-wildcard return nil, err
// prefix.
d1, d2 := splitWildcards(p)
if d1 != "/" {
origPrefix = d1
k = convertPathToKey([]byte(d1))
linksWalked := 0
if d2 != "" {
// getFollowLinks only handles symlinks in path
// components before the last component, so
// handle last component in d1 specially.
for {
v, ok := root.Get(k)
if !ok || v.(*CacheRecord).Type != CacheRecordTypeSymlink {
break
}
linksWalked++
if linksWalked > 255 {
return nil, errors.Errorf("too many links")
}
dirPath := path.Clean(d1)
if dirPath == "." || dirPath == "/" {
dirPath = ""
}
d1 = path.Clean(v.(*CacheRecord).Linkname)
if !path.IsAbs(d1) {
d1 = path.Clean(path.Join("/", path.Join(path.Dir(dirPath), d1)))
}
k = convertPathToKey([]byte(d1))
}
}
} }
} else { } else {
origPrefix = p origPrefix = p
k = convertPathToKey([]byte(origPrefix)) k = convertPathToKey([]byte(origPrefix))
}
if origPrefix != "" {
// We need to resolve symlinks here, in case the base path // We need to resolve symlinks here, in case the base path
// involves a symlink. That will match fsutil behavior of // involves a symlink. That will match fsutil behavior of
// calling functions such as stat and walk. // calling functions such as stat and walk.
@ -569,8 +533,11 @@ func (cc *cacheContext) includedPaths(ctx context.Context, m *mount, p string, o
if err != nil { if err != nil {
return nil, err return nil, err
} }
kOk = (cr != nil)
}
if kOk = (cr != nil); kOk { if origPrefix != "" {
if kOk {
iter.SeekLowerBound(append(append([]byte{}, k...), 0)) iter.SeekLowerBound(append(append([]byte{}, k...), 0))
} }
@ -717,6 +684,50 @@ func shouldIncludePath(
return true, nil return true, nil
} }
func wildcardPrefix(root *iradix.Node, p string) (string, []byte, bool, error) {
// For consistency with what the copy implementation in fsutil
// does: split pattern into non-wildcard prefix and rest of
// pattern, then follow symlinks when resolving the non-wildcard
// prefix.
d1, d2 := splitWildcards(p)
if d1 == "/" {
return "", nil, false, nil
}
k, cr, err := getFollowLinks(root, convertPathToKey([]byte(d1)), true)
if err != nil {
return "", k, false, err
}
if d2 != "" && cr != nil && cr.Type == CacheRecordTypeSymlink {
// getFollowLinks only handles symlinks in path
// components before the last component, so
// handle last component in d1 specially.
linksWalked := 0
resolved := string(convertKeyToPath(k))
for {
v, ok := root.Get(k)
if !ok {
return d1, k, false, nil
}
if v.(*CacheRecord).Type != CacheRecordTypeSymlink {
break
}
linksWalked++
if linksWalked > 255 {
return "", k, false, errors.Errorf("too many links")
}
resolved := cleanLink(resolved, v.(*CacheRecord).Linkname)
k = convertPathToKey([]byte(resolved))
}
}
return d1, k, cr != nil, nil
}
func splitWildcards(p string) (d1, d2 string) { func splitWildcards(p string) (d1, d2 string) {
parts := strings.Split(path.Join(p), "/") parts := strings.Split(path.Join(p), "/")
var p1, p2 []string var p1, p2 []string
@ -1030,14 +1041,8 @@ func getFollowLinksWalk(root *iradix.Node, k []byte, follow bool, linksWalked *i
if *linksWalked > 255 { if *linksWalked > 255 {
return nil, nil, errors.Errorf("too many links") return nil, nil, errors.Errorf("too many links")
} }
dirPath := path.Clean(string(convertKeyToPath(dir)))
if dirPath == "." || dirPath == "/" { link := cleanLink(string(convertKeyToPath(dir)), parent.Linkname)
dirPath = ""
}
link := path.Clean(parent.Linkname)
if !path.IsAbs(link) {
link = path.Join("/", path.Join(path.Dir(dirPath), link))
}
return getFollowLinksWalk(root, append(convertPathToKey([]byte(link)), file...), follow, linksWalked) return getFollowLinksWalk(root, append(convertPathToKey([]byte(link)), file...), follow, linksWalked)
} }
} }
@ -1049,6 +1054,18 @@ func getFollowLinksWalk(root *iradix.Node, k []byte, follow bool, linksWalked *i
return k, nil, nil return k, nil, nil
} }
func cleanLink(dir, linkname string) string {
dirPath := path.Clean(dir)
if dirPath == "." || dirPath == "/" {
dirPath = ""
}
link := path.Clean(linkname)
if !path.IsAbs(link) {
return path.Join("/", path.Join(path.Dir(dirPath), link))
}
return link
}
func prepareDigest(fp, p string, fi os.FileInfo) (digest.Digest, error) { func prepareDigest(fp, p string, fi os.FileInfo) (digest.Digest, error) {
h, err := NewFileHash(fp, fi) h, err := NewFileHash(fp, fi)
if err != nil { if err != nil {

View File

@ -657,6 +657,7 @@ func TestChecksumIncludeSymlink(t *testing.T) {
"ADD mnt dir", "ADD mnt dir",
"ADD mnt/data symlink ../data", "ADD mnt/data symlink ../data",
"ADD data/d0/d1/d2/foo file abc", "ADD data/d0/d1/d2/foo file abc",
"ADD data/symlink-to-d0 symlink d0",
} }
ref := createRef(t, cm, ch) ref := createRef(t, cm, ch)
@ -704,6 +705,10 @@ func TestChecksumIncludeSymlink(t *testing.T) {
dgstMntInnerWildcard, err := cc.Checksum(context.TODO(), ref, "mnt/data/d0/d*/d2", ChecksumOpts{IncludePatterns: []string{"**/foo"}, Wildcard: true}, nil) dgstMntInnerWildcard, err := cc.Checksum(context.TODO(), ref, "mnt/data/d0/d*/d2", ChecksumOpts{IncludePatterns: []string{"**/foo"}, Wildcard: true}, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, dgstD2, dgstMntInnerWildcard) require.Equal(t, dgstD2, dgstMntInnerWildcard)
dgstMntInnerWildcard2, err := cc.Checksum(context.TODO(), ref, "mnt/data/symlink-to-d0/d*/d2", ChecksumOpts{IncludePatterns: []string{"**/foo"}, Wildcard: true}, nil)
require.NoError(t, err)
require.Equal(t, dgstD2, dgstMntInnerWildcard2)
} }
func TestHandleChange(t *testing.T) { func TestHandleChange(t *testing.T) {