mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 16:13:45 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			151 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			151 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package fsutil
 | |
| 
 | |
| import (
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"runtime"
 | |
| 	"sort"
 | |
| 	strings "strings"
 | |
| 
 | |
| 	"github.com/pkg/errors"
 | |
| )
 | |
| 
 | |
| func FollowLinks(root string, paths []string) ([]string, error) {
 | |
| 	r := &symlinkResolver{root: root, 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, r)
 | |
| 	}
 | |
| 	sort.Strings(res)
 | |
| 	return dedupePaths(res), nil
 | |
| }
 | |
| 
 | |
| type symlinkResolver struct {
 | |
| 	root     string
 | |
| 	resolved map[string]struct{}
 | |
| }
 | |
| 
 | |
| func (r *symlinkResolver) append(p string) error {
 | |
| 	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) {
 | |
| 	realPath := filepath.Join(r.root, p)
 | |
| 	base := filepath.Base(p)
 | |
| 	if allowWildcard && containsWildcards(base) {
 | |
| 		fis, err := ioutil.ReadDir(filepath.Dir(realPath))
 | |
| 		if err != nil {
 | |
| 			if os.IsNotExist(err) {
 | |
| 				return nil, nil
 | |
| 			}
 | |
| 			return nil, errors.Wrapf(err, "failed to read dir %s", filepath.Dir(realPath))
 | |
| 		}
 | |
| 		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
 | |
| 	}
 | |
| 
 | |
| 	fi, err := os.Lstat(realPath)
 | |
| 	if err != nil {
 | |
| 		if os.IsNotExist(err) {
 | |
| 			return nil, nil
 | |
| 		}
 | |
| 		return nil, errors.Wrapf(err, "failed to lstat %s", realPath)
 | |
| 	}
 | |
| 	if fi.Mode()&os.ModeSymlink == 0 {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	link, err := os.Readlink(realPath)
 | |
| 	if err != nil {
 | |
| 		return nil, errors.Wrapf(err, "failed to readlink %s", realPath)
 | |
| 	}
 | |
| 	link = filepath.Clean(link)
 | |
| 	if filepath.IsAbs(link) {
 | |
| 		return []string{link}, nil
 | |
| 	}
 | |
| 	return []string{
 | |
| 		filepath.Join(string(filepath.Separator), filepath.Join(filepath.Dir(p), link)),
 | |
| 	}, 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+string(filepath.Separator)) {
 | |
| 			continue
 | |
| 		}
 | |
| 		out = append(out, s)
 | |
| 		last = s
 | |
| 	}
 | |
| 	return out
 | |
| }
 | 
