mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-28 16:37:43 +08:00

full diff: -36ef4d8c0d...f098008783
-d5c1d785b0...5ae9b23c40
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
252 lines
5.4 KiB
Go
252 lines
5.4 KiB
Go
package fsutil
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
gofs "io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/tonistiigi/fsutil/types"
|
|
)
|
|
|
|
type FS interface {
|
|
Walk(context.Context, string, gofs.WalkDirFunc) error
|
|
Open(string) (io.ReadCloser, error)
|
|
}
|
|
|
|
// NewFS creates a new FS from a root directory on the host filesystem.
|
|
func NewFS(root string) (FS, error) {
|
|
root, err := filepath.EvalSymlinks(root)
|
|
if err != nil {
|
|
return nil, errors.WithStack(&os.PathError{Op: "resolve", Path: root, Err: err})
|
|
}
|
|
fi, err := os.Stat(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !fi.IsDir() {
|
|
return nil, errors.WithStack(&os.PathError{Op: "stat", Path: root, Err: syscall.ENOTDIR})
|
|
}
|
|
|
|
return &fs{
|
|
root: root,
|
|
}, nil
|
|
}
|
|
|
|
type fs struct {
|
|
root string
|
|
}
|
|
|
|
func (fs *fs) Walk(ctx context.Context, target string, fn gofs.WalkDirFunc) error {
|
|
seenFiles := make(map[uint64]string)
|
|
return filepath.WalkDir(filepath.Join(fs.root, target), func(path string, dirEntry gofs.DirEntry, walkErr error) (retErr error) {
|
|
defer func() {
|
|
if retErr != nil && isNotExist(retErr) {
|
|
retErr = filepath.SkipDir
|
|
}
|
|
}()
|
|
|
|
origpath := path
|
|
path, err := filepath.Rel(fs.root, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Skip root
|
|
if path == "." {
|
|
return nil
|
|
}
|
|
|
|
var entry gofs.DirEntry
|
|
if dirEntry != nil {
|
|
entry = &DirEntryInfo{
|
|
path: path,
|
|
origpath: origpath,
|
|
entry: dirEntry,
|
|
seenFiles: seenFiles,
|
|
}
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
if err := fn(path, entry, walkErr); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (fs *fs) Open(p string) (io.ReadCloser, error) {
|
|
rc, err := os.Open(filepath.Join(fs.root, p))
|
|
return rc, errors.WithStack(err)
|
|
}
|
|
|
|
type Dir struct {
|
|
Stat types.Stat
|
|
FS FS
|
|
}
|
|
|
|
func SubDirFS(dirs []Dir) (FS, error) {
|
|
sort.Slice(dirs, func(i, j int) bool {
|
|
return dirs[i].Stat.Path < dirs[j].Stat.Path
|
|
})
|
|
m := map[string]Dir{}
|
|
for _, d := range dirs {
|
|
if path.Base(d.Stat.Path) != d.Stat.Path {
|
|
return nil, errors.WithStack(&os.PathError{Path: d.Stat.Path, Err: syscall.EISDIR, Op: "invalid path"})
|
|
}
|
|
if _, ok := m[d.Stat.Path]; ok {
|
|
return nil, errors.WithStack(&os.PathError{Path: d.Stat.Path, Err: syscall.EEXIST, Op: "duplicate path"})
|
|
}
|
|
m[d.Stat.Path] = d
|
|
}
|
|
return &subDirFS{m: m, dirs: dirs}, nil
|
|
}
|
|
|
|
type subDirFS struct {
|
|
m map[string]Dir
|
|
dirs []Dir
|
|
}
|
|
|
|
func (fs *subDirFS) Walk(ctx context.Context, target string, fn gofs.WalkDirFunc) error {
|
|
first, rest, _ := strings.Cut(target, string(filepath.Separator))
|
|
|
|
for _, d := range fs.dirs {
|
|
if first != "" && first != d.Stat.Path {
|
|
continue
|
|
}
|
|
|
|
fi := &StatInfo{&d.Stat}
|
|
if !fi.IsDir() {
|
|
return errors.WithStack(&os.PathError{Path: d.Stat.Path, Err: syscall.ENOTDIR, Op: "walk subdir"})
|
|
}
|
|
dStat := d.Stat
|
|
if err := fn(d.Stat.Path, &DirEntryInfo{Stat: &dStat}, nil); err != nil {
|
|
return err
|
|
}
|
|
if err := d.FS.Walk(ctx, rest, func(p string, entry gofs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fi, err := entry.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stat, ok := fi.Sys().(*types.Stat)
|
|
if !ok {
|
|
return errors.WithStack(&os.PathError{Path: d.Stat.Path, Err: syscall.EBADMSG, Op: "fileinfo without stat info"})
|
|
}
|
|
stat.Path = path.Join(d.Stat.Path, stat.Path)
|
|
if stat.Linkname != "" {
|
|
if fi.Mode()&os.ModeSymlink != 0 {
|
|
if strings.HasPrefix(stat.Linkname, "/") {
|
|
stat.Linkname = path.Join("/"+d.Stat.Path, stat.Linkname)
|
|
}
|
|
} else {
|
|
stat.Linkname = path.Join(d.Stat.Path, stat.Linkname)
|
|
}
|
|
}
|
|
return fn(filepath.Join(d.Stat.Path, p), &DirEntryInfo{Stat: stat}, nil)
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (fs *subDirFS) Open(p string) (io.ReadCloser, error) {
|
|
parts := strings.SplitN(filepath.Clean(p), string(filepath.Separator), 2)
|
|
if len(parts) == 0 {
|
|
return io.NopCloser(&emptyReader{}), nil
|
|
}
|
|
d, ok := fs.m[parts[0]]
|
|
if !ok {
|
|
return nil, errors.WithStack(&os.PathError{Path: parts[0], Err: syscall.ENOENT, Op: "open"})
|
|
}
|
|
return d.FS.Open(parts[1])
|
|
}
|
|
|
|
type emptyReader struct {
|
|
}
|
|
|
|
func (*emptyReader) Read([]byte) (int, error) {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
type StatInfo struct {
|
|
*types.Stat
|
|
}
|
|
|
|
func (s *StatInfo) Name() string {
|
|
return filepath.Base(s.Stat.Path)
|
|
}
|
|
func (s *StatInfo) Size() int64 {
|
|
return s.Stat.Size_
|
|
}
|
|
func (s *StatInfo) Mode() os.FileMode {
|
|
return os.FileMode(s.Stat.Mode)
|
|
}
|
|
func (s *StatInfo) ModTime() time.Time {
|
|
return time.Unix(s.Stat.ModTime/1e9, s.Stat.ModTime%1e9)
|
|
}
|
|
func (s *StatInfo) IsDir() bool {
|
|
return s.Mode().IsDir()
|
|
}
|
|
func (s *StatInfo) Sys() interface{} {
|
|
return s.Stat
|
|
}
|
|
|
|
type DirEntryInfo struct {
|
|
*types.Stat
|
|
|
|
entry gofs.DirEntry
|
|
path string
|
|
origpath string
|
|
seenFiles map[uint64]string
|
|
}
|
|
|
|
func (s *DirEntryInfo) Name() string {
|
|
if s.Stat != nil {
|
|
return filepath.Base(s.Stat.Path)
|
|
}
|
|
return s.entry.Name()
|
|
}
|
|
func (s *DirEntryInfo) IsDir() bool {
|
|
if s.Stat != nil {
|
|
return s.Stat.IsDir()
|
|
}
|
|
return s.entry.IsDir()
|
|
}
|
|
func (s *DirEntryInfo) Type() gofs.FileMode {
|
|
if s.Stat != nil {
|
|
return gofs.FileMode(s.Stat.Mode)
|
|
}
|
|
return s.entry.Type()
|
|
}
|
|
func (s *DirEntryInfo) Info() (gofs.FileInfo, error) {
|
|
if s.Stat == nil {
|
|
fi, err := s.entry.Info()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stat, err := mkstat(s.origpath, s.path, fi, s.seenFiles)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.Stat = stat
|
|
}
|
|
|
|
st := *s.Stat
|
|
return &StatInfo{&st}, nil
|
|
}
|