Sebastiaan van Stijn c855277d53
vendor: github.com/moby/buildkit 5ae9b23c40a9 (master / v0.13.0-dev)
full diff:

- 36ef4d8c0d...f098008783
- d5c1d785b0...5ae9b23c40

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-11-15 15:59:23 +01:00

237 lines
4.8 KiB
Go

package fsutil
import (
"context"
gofs "io/fs"
"os"
"path/filepath"
"runtime"
"sort"
strings "strings"
"syscall"
"github.com/pkg/errors"
"github.com/tonistiigi/fsutil/types"
)
func FollowLinks(fs FS, paths []string) ([]string, error) {
r := &symlinkResolver{fs: fs, resolved: map[string]struct{}{}}
for _, p := range paths {
if err := r.append(p); err != nil {
return nil, err
}
}
res := make([]string, 0, len(r.resolved))
for r := range r.resolved {
res = append(res, filepath.ToSlash(r))
}
sort.Strings(res)
return dedupePaths(res), nil
}
type symlinkResolver struct {
fs FS
resolved map[string]struct{}
}
func (r *symlinkResolver) append(p string) error {
if runtime.GOOS == "windows" && filepath.IsAbs(filepath.FromSlash(p)) {
absParts := strings.SplitN(p, ":", 2)
if len(absParts) == 2 {
p = absParts[1]
}
}
p = filepath.Join(".", p)
current := "."
for {
parts := strings.SplitN(p, string(filepath.Separator), 2)
current = filepath.Join(current, parts[0])
targets, err := r.readSymlink(current, true)
if err != nil {
return err
}
p = ""
if len(parts) == 2 {
p = parts[1]
}
if p == "" || targets != nil {
if _, ok := r.resolved[current]; ok {
return nil
}
}
if targets != nil {
r.resolved[current] = struct{}{}
for _, target := range targets {
if err := r.append(filepath.Join(target, p)); err != nil {
return err
}
}
return nil
}
if p == "" {
r.resolved[current] = struct{}{}
return nil
}
}
}
func (r *symlinkResolver) readSymlink(p string, allowWildcard bool) ([]string, error) {
base := filepath.Base(p)
if allowWildcard && containsWildcards(base) {
fis, err := readDir(r.fs, filepath.Dir(p))
if err != nil {
if isNotFound(err) {
return nil, nil
}
return nil, errors.Wrap(err, "readdir")
}
var out []string
for _, f := range fis {
if ok, _ := filepath.Match(base, f.Name()); ok {
res, err := r.readSymlink(filepath.Join(filepath.Dir(p), f.Name()), false)
if err != nil {
return nil, err
}
out = append(out, res...)
}
}
return out, nil
}
entry, err := statFile(r.fs, p)
if err != nil {
if isNotFound(err) {
return nil, nil
}
return nil, errors.WithStack(err)
}
if entry == nil {
return nil, nil
}
if entry.Type()&os.ModeSymlink == 0 {
return nil, nil
}
fi, err := entry.Info()
if err != nil {
return nil, errors.WithStack(err)
}
stat, ok := fi.Sys().(*types.Stat)
if !ok {
return nil, errors.WithStack(&os.PathError{Path: p, Err: syscall.EBADMSG, Op: "fileinfo without stat info"})
}
link := filepath.Clean(stat.Linkname)
if filepath.IsAbs(link) {
return []string{link}, nil
}
return []string{
filepath.Join(string(filepath.Separator), filepath.Join(filepath.Dir(p), link)),
}, nil
}
func statFile(fs FS, root string) (os.DirEntry, error) {
var out os.DirEntry
root = filepath.Clean(root)
if root == "/" || root == "." {
return nil, nil
}
err := fs.Walk(context.TODO(), root, func(p string, entry os.DirEntry, err error) error {
if err != nil {
return err
}
if p != root {
return errors.Errorf("expected single entry %q but got %q", root, p)
}
out = entry
if entry.IsDir() {
return filepath.SkipDir
}
return nil
})
if err != nil {
return nil, err
}
if out == nil {
return nil, errors.Wrapf(os.ErrNotExist, "readFile %s", root)
}
return out, nil
}
func readDir(fs FS, root string) ([]os.DirEntry, error) {
var out []os.DirEntry
root = filepath.Clean(root)
if root == "/" || root == "." {
root = "."
out = make([]gofs.DirEntry, 0)
}
err := fs.Walk(context.TODO(), root, func(p string, entry os.DirEntry, err error) error {
if err != nil {
return err
}
if p == root {
if !entry.IsDir() {
return errors.WithStack(&os.PathError{Op: "walk", Path: root, Err: syscall.ENOTDIR})
}
out = make([]gofs.DirEntry, 0)
return nil
}
if out == nil {
return errors.Errorf("expected to read parent entry %q before child %q", root, p)
}
out = append(out, entry)
if entry.IsDir() {
return filepath.SkipDir
}
return nil
})
if err != nil {
return nil, err
}
if out == nil && root != "." {
return nil, errors.Wrapf(os.ErrNotExist, "readDir %s", root)
}
return out, nil
}
func containsWildcards(name string) bool {
isWindows := runtime.GOOS == "windows"
for i := 0; i < len(name); i++ {
ch := name[i]
if ch == '\\' && !isWindows {
i++
} else if ch == '*' || ch == '?' || ch == '[' {
return true
}
}
return false
}
// dedupePaths expects input as a sorted list
func dedupePaths(in []string) []string {
out := make([]string, 0, len(in))
var last string
for _, s := range in {
// if one of the paths is root there is no filter
if s == "." {
return nil
}
if strings.HasPrefix(s, last+"/") {
continue
}
out = append(out, s)
last = s
}
return out
}