Browse Source

Merge pull request #38917 from tonistiigi/vendor-buildkit-fileop

vendor: update buildkit to e5b647a1a
Vincent Demeester 6 years ago
parent
commit
86ef34b521
40 changed files with 3879 additions and 648 deletions
  1. 2 0
      builder/builder-next/worker/worker.go
  2. 2 2
      vendor.conf
  3. 49 1
      vendor/github.com/moby/buildkit/cache/contenthash/checksum.go
  4. 1 1
      vendor/github.com/moby/buildkit/cache/refs.go
  5. 727 0
      vendor/github.com/moby/buildkit/client/llb/fileop.go
  6. 11 0
      vendor/github.com/moby/buildkit/client/llb/state.go
  7. 1 1
      vendor/github.com/moby/buildkit/client/solve.go
  8. 122 6
      vendor/github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb/convert.go
  9. 2 2
      vendor/github.com/moby/buildkit/session/filesync/diffcopy.go
  10. 1 1
      vendor/github.com/moby/buildkit/session/filesync/filesync.go
  11. 266 0
      vendor/github.com/moby/buildkit/solver/llbsolver/file/backend.go
  12. 71 0
      vendor/github.com/moby/buildkit/solver/llbsolver/file/refmanager.go
  13. 61 0
      vendor/github.com/moby/buildkit/solver/llbsolver/file/unpack.go
  14. 119 0
      vendor/github.com/moby/buildkit/solver/llbsolver/file/user_linux.go
  15. 14 0
      vendor/github.com/moby/buildkit/solver/llbsolver/file/user_nolinux.go
  16. 9 1
      vendor/github.com/moby/buildkit/solver/llbsolver/ops/exec.go
  17. 580 0
      vendor/github.com/moby/buildkit/solver/llbsolver/ops/file.go
  18. 28 0
      vendor/github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes/types.go
  19. 20 6
      vendor/github.com/moby/buildkit/solver/llbsolver/result.go
  20. 21 0
      vendor/github.com/moby/buildkit/solver/llbsolver/vertex.go
  21. 2 0
      vendor/github.com/moby/buildkit/solver/pb/attr.go
  22. 12 0
      vendor/github.com/moby/buildkit/solver/pb/caps.go
  23. 806 607
      vendor/github.com/moby/buildkit/solver/pb/ops.pb.go
  24. 98 13
      vendor/github.com/moby/buildkit/solver/pb/ops.proto
  25. 1 1
      vendor/github.com/moby/buildkit/util/flightcontrol/flightcontrol.go
  26. 11 1
      vendor/github.com/moby/buildkit/util/imageutil/config.go
  27. 423 0
      vendor/github.com/tonistiigi/fsutil/copy/copy.go
  28. 97 0
      vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go
  29. 28 0
      vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go
  30. 68 0
      vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go
  31. 33 0
      vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go
  32. 27 0
      vendor/github.com/tonistiigi/fsutil/copy/hardlink.go
  33. 17 0
      vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go
  34. 7 0
      vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go
  35. 74 0
      vendor/github.com/tonistiigi/fsutil/copy/mkdir.go
  36. 32 0
      vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go
  37. 21 0
      vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go
  38. 8 2
      vendor/github.com/tonistiigi/fsutil/diskwriter.go
  39. 5 1
      vendor/github.com/tonistiigi/fsutil/stat_unix.go
  40. 2 2
      vendor/github.com/tonistiigi/fsutil/walker.go

+ 2 - 0
builder/builder-next/worker/worker.go

@@ -165,6 +165,8 @@ func (w *Worker) ResolveOp(v solver.Vertex, s frontend.FrontendLLBBridge, sm *se
 			return ops.NewSourceOp(v, op, baseOp.Platform, w.SourceManager, sm, w)
 			return ops.NewSourceOp(v, op, baseOp.Platform, w.SourceManager, sm, w)
 		case *pb.Op_Exec:
 		case *pb.Op_Exec:
 			return ops.NewExecOp(v, op, baseOp.Platform, w.CacheManager, sm, w.MetadataStore, w.Executor, w)
 			return ops.NewExecOp(v, op, baseOp.Platform, w.CacheManager, sm, w.MetadataStore, w.Executor, w)
+		case *pb.Op_File:
+			return ops.NewFileOp(v, op, w.CacheManager, w.MetadataStore, w)
 		case *pb.Op_Build:
 		case *pb.Op_Build:
 			return ops.NewBuildOp(v, op, s, w)
 			return ops.NewBuildOp(v, op, s, w)
 		}
 		}

+ 2 - 2
vendor.conf

@@ -27,8 +27,8 @@ github.com/imdario/mergo v0.3.6
 golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca
 golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca
 
 
 # buildkit
 # buildkit
-github.com/moby/buildkit c35410878ab9070498c66f6c67d3e8bc3b92241f
-github.com/tonistiigi/fsutil 1ec1983587cde7e8ac2978e354ff5360af622464
+github.com/moby/buildkit e9aca5bef87e19173b99d8668db0338dcaaa5f33
+github.com/tonistiigi/fsutil 1bdbf124ad494a771e99e0cdcd16326375f8b2c9
 github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
 github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
 github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
 github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7
 github.com/google/shlex 6f45313302b9c56850fc17f99e40caebce98c716
 github.com/google/shlex 6f45313302b9c56850fc17f99e40caebce98c716

+ 49 - 1
vendor/github.com/moby/buildkit/cache/contenthash/checksum.go

@@ -47,6 +47,10 @@ func Checksum(ctx context.Context, ref cache.ImmutableRef, path string, followLi
 	return getDefaultManager().Checksum(ctx, ref, path, followLinks)
 	return getDefaultManager().Checksum(ctx, ref, path, followLinks)
 }
 }
 
 
+func ChecksumWildcard(ctx context.Context, ref cache.ImmutableRef, path string, followLinks bool) (digest.Digest, error) {
+	return getDefaultManager().ChecksumWildcard(ctx, ref, path, followLinks)
+}
+
 func GetCacheContext(ctx context.Context, md *metadata.StorageItem) (CacheContext, error) {
 func GetCacheContext(ctx context.Context, md *metadata.StorageItem) (CacheContext, error) {
 	return getDefaultManager().GetCacheContext(ctx, md)
 	return getDefaultManager().GetCacheContext(ctx, md)
 }
 }
@@ -84,6 +88,14 @@ func (cm *cacheManager) Checksum(ctx context.Context, ref cache.ImmutableRef, p
 	return cc.Checksum(ctx, ref, p, followLinks)
 	return cc.Checksum(ctx, ref, p, followLinks)
 }
 }
 
 
+func (cm *cacheManager) ChecksumWildcard(ctx context.Context, ref cache.ImmutableRef, p string, followLinks bool) (digest.Digest, error) {
+	cc, err := cm.GetCacheContext(ctx, ensureOriginMetadata(ref.Metadata()))
+	if err != nil {
+		return "", nil
+	}
+	return cc.ChecksumWildcard(ctx, ref, p, followLinks)
+}
+
 func (cm *cacheManager) GetCacheContext(ctx context.Context, md *metadata.StorageItem) (CacheContext, error) {
 func (cm *cacheManager) GetCacheContext(ctx context.Context, md *metadata.StorageItem) (CacheContext, error) {
 	cm.locker.Lock(md.ID())
 	cm.locker.Lock(md.ID())
 	cm.lruMu.Lock()
 	cm.lruMu.Lock()
@@ -91,6 +103,7 @@ func (cm *cacheManager) GetCacheContext(ctx context.Context, md *metadata.Storag
 	cm.lruMu.Unlock()
 	cm.lruMu.Unlock()
 	if ok {
 	if ok {
 		cm.locker.Unlock(md.ID())
 		cm.locker.Unlock(md.ID())
+		v.(*cacheContext).linkMap = map[string][][]byte{}
 		return v.(*cacheContext), nil
 		return v.(*cacheContext), nil
 	}
 	}
 	cc, err := newCacheContext(md)
 	cc, err := newCacheContext(md)
@@ -115,6 +128,7 @@ func (cm *cacheManager) SetCacheContext(ctx context.Context, md *metadata.Storag
 			md:       md,
 			md:       md,
 			tree:     cci.(*cacheContext).tree,
 			tree:     cci.(*cacheContext).tree,
 			dirtyMap: map[string]struct{}{},
 			dirtyMap: map[string]struct{}{},
+			linkMap:  map[string][][]byte{},
 		}
 		}
 	} else {
 	} else {
 		if err := cc.save(); err != nil {
 		if err := cc.save(); err != nil {
@@ -137,6 +151,7 @@ type cacheContext struct {
 	txn      *iradix.Txn
 	txn      *iradix.Txn
 	node     *iradix.Node
 	node     *iradix.Node
 	dirtyMap map[string]struct{}
 	dirtyMap map[string]struct{}
+	linkMap  map[string][][]byte
 }
 }
 
 
 type mount struct {
 type mount struct {
@@ -181,6 +196,7 @@ func newCacheContext(md *metadata.StorageItem) (*cacheContext, error) {
 		md:       md,
 		md:       md,
 		tree:     iradix.New(),
 		tree:     iradix.New(),
 		dirtyMap: map[string]struct{}{},
 		dirtyMap: map[string]struct{}{},
+		linkMap:  map[string][][]byte{},
 	}
 	}
 	if err := cc.load(); err != nil {
 	if err := cc.load(); err != nil {
 		return nil, err
 		return nil, err
@@ -313,7 +329,35 @@ func (cc *cacheContext) HandleChange(kind fsutil.ChangeKind, p string, fi os.Fil
 		p += "/"
 		p += "/"
 	}
 	}
 	cr.Digest = h.Digest()
 	cr.Digest = h.Digest()
+
+	// if we receive a hardlink just use the digest of the source
+	// note that the source may be called later because data writing is async
+	if fi.Mode()&os.ModeSymlink == 0 && stat.Linkname != "" {
+		ln := path.Join("/", filepath.ToSlash(stat.Linkname))
+		v, ok := cc.txn.Get(convertPathToKey([]byte(ln)))
+		if ok {
+			cp := *v.(*CacheRecord)
+			cr = &cp
+		}
+		cc.linkMap[ln] = append(cc.linkMap[ln], k)
+	}
+
 	cc.txn.Insert(k, cr)
 	cc.txn.Insert(k, cr)
+	if !fi.IsDir() {
+		if links, ok := cc.linkMap[p]; ok {
+			for _, l := range links {
+				pp := convertKeyToPath(l)
+				cc.txn.Insert(l, cr)
+				d := path.Dir(string(pp))
+				if d == "/" {
+					d = ""
+				}
+				cc.dirtyMap[d] = struct{}{}
+			}
+			delete(cc.linkMap, p)
+		}
+	}
+
 	d := path.Dir(p)
 	d := path.Dir(p)
 	if d == "/" {
 	if d == "/" {
 		d = ""
 		d = ""
@@ -343,6 +387,9 @@ func (cc *cacheContext) ChecksumWildcard(ctx context.Context, mountable cache.Mo
 			}
 			}
 		}
 		}
 	}
 	}
+	if len(wildcards) == 0 {
+		return digest.FromBytes([]byte{}), nil
+	}
 
 
 	if len(wildcards) > 1 {
 	if len(wildcards) > 1 {
 		digester := digest.Canonical.Digester()
 		digester := digest.Canonical.Digester()
@@ -543,12 +590,13 @@ func (cc *cacheContext) lazyChecksum(ctx context.Context, m *mount, p string) (*
 }
 }
 
 
 func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, follow bool) (*CacheRecord, bool, error) {
 func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node, txn *iradix.Txn, m *mount, k []byte, follow bool) (*CacheRecord, bool, error) {
+	origk := k
 	k, cr, err := getFollowLinks(root, k, follow)
 	k, cr, err := getFollowLinks(root, k, follow)
 	if err != nil {
 	if err != nil {
 		return nil, false, err
 		return nil, false, err
 	}
 	}
 	if cr == nil {
 	if cr == nil {
-		return nil, false, errors.Wrapf(errNotFound, "%s not found", convertKeyToPath(k))
+		return nil, false, errors.Wrapf(errNotFound, "%q not found", convertKeyToPath(origk))
 	}
 	}
 	if cr.Digest != "" {
 	if cr.Digest != "" {
 		return cr, false, nil
 		return cr, false, nil

+ 1 - 1
vendor/github.com/moby/buildkit/cache/refs.go

@@ -311,7 +311,7 @@ func (sr *mutableRef) updateLastUsed() bool {
 
 
 func (sr *mutableRef) commit(ctx context.Context) (ImmutableRef, error) {
 func (sr *mutableRef) commit(ctx context.Context) (ImmutableRef, error) {
 	if !sr.mutable || len(sr.refs) == 0 {
 	if !sr.mutable || len(sr.refs) == 0 {
-		return nil, errors.Wrapf(errInvalid, "invalid mutable ref")
+		return nil, errors.Wrapf(errInvalid, "invalid mutable ref %p", sr)
 	}
 	}
 
 
 	id := identity.NewID()
 	id := identity.NewID()

+ 727 - 0
vendor/github.com/moby/buildkit/client/llb/fileop.go

@@ -0,0 +1,727 @@
+package llb
+
+import (
+	_ "crypto/sha256"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/moby/buildkit/solver/pb"
+	digest "github.com/opencontainers/go-digest"
+	"github.com/pkg/errors"
+)
+
+// Examples:
+// local := llb.Local(...)
+// llb.Image().Dir("/abc").File(Mkdir("./foo").Mkfile("/abc/foo/bar", []byte("data")))
+// llb.Image().File(Mkdir("/foo").Mkfile("/foo/bar", []byte("data")))
+// llb.Image().File(Copy(local, "/foo", "/bar")).File(Copy(local, "/foo2", "/bar2"))
+//
+// a := Mkdir("./foo")  // *FileAction /ced/foo
+// b := Mkdir("./bar") // /abc/bar
+// c := b.Copy(a.WithState(llb.Scratch().Dir("/ced")), "./foo", "./baz") // /abc/baz
+// llb.Image().Dir("/abc").File(c)
+//
+// In future this can be extended to multiple outputs with:
+// a := Mkdir("./foo")
+// b, id := a.GetSelector()
+// c := b.Mkdir("./bar")
+// filestate = state.File(c)
+// filestate.GetOutput(id).Exec()
+
+func NewFileOp(s State, action *FileAction, c Constraints) *FileOp {
+	action = action.bind(s)
+
+	f := &FileOp{
+		action:      action,
+		constraints: c,
+	}
+
+	f.output = &output{vertex: f, getIndex: func() (pb.OutputIndex, error) {
+		return pb.OutputIndex(0), nil
+	}}
+
+	return f
+}
+
+// CopyInput is either llb.State or *FileActionWithState
+type CopyInput interface {
+	isFileOpCopyInput()
+}
+
+type subAction interface {
+	toProtoAction(string, pb.InputIndex) pb.IsFileAction
+}
+
+type FileAction struct {
+	state  *State
+	prev   *FileAction
+	action subAction
+	err    error
+}
+
+func (fa *FileAction) Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction {
+	a := Mkdir(p, m, opt...)
+	a.prev = fa
+	return a
+}
+
+func (fa *FileAction) Mkfile(p string, m os.FileMode, dt []byte, opt ...MkfileOption) *FileAction {
+	a := Mkfile(p, m, dt, opt...)
+	a.prev = fa
+	return a
+}
+
+func (fa *FileAction) Rm(p string, opt ...RmOption) *FileAction {
+	a := Rm(p, opt...)
+	a.prev = fa
+	return a
+}
+
+func (fa *FileAction) Copy(input CopyInput, src, dest string, opt ...CopyOption) *FileAction {
+	a := Copy(input, src, dest, opt...)
+	a.prev = fa
+	return a
+}
+
+func (fa *FileAction) allOutputs(m map[Output]struct{}) {
+	if fa == nil {
+		return
+	}
+	if fa.state != nil && fa.state.Output() != nil {
+		m[fa.state.Output()] = struct{}{}
+	}
+
+	if a, ok := fa.action.(*fileActionCopy); ok {
+		if a.state != nil {
+			if out := a.state.Output(); out != nil {
+				m[out] = struct{}{}
+			}
+		} else if a.fas != nil {
+			a.fas.allOutputs(m)
+		}
+	}
+	fa.prev.allOutputs(m)
+}
+
+func (fa *FileAction) bind(s State) *FileAction {
+	if fa == nil {
+		return nil
+	}
+	fa2 := *fa
+	fa2.prev = fa.prev.bind(s)
+	fa2.state = &s
+	return &fa2
+}
+
+func (fa *FileAction) WithState(s State) CopyInput {
+	return &fileActionWithState{FileAction: fa.bind(s)}
+}
+
+type fileActionWithState struct {
+	*FileAction
+}
+
+func (fas *fileActionWithState) isFileOpCopyInput() {}
+
+func Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction {
+	var mi MkdirInfo
+	for _, o := range opt {
+		o.SetMkdirOption(&mi)
+	}
+	return &FileAction{
+		action: &fileActionMkdir{
+			file: p,
+			mode: m,
+			info: mi,
+		},
+	}
+}
+
+type fileActionMkdir struct {
+	file string
+	mode os.FileMode
+	info MkdirInfo
+}
+
+func (a *fileActionMkdir) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
+	return &pb.FileAction_Mkdir{
+		Mkdir: &pb.FileActionMkDir{
+			Path:        normalizePath(parent, a.file, false),
+			Mode:        int32(a.mode & 0777),
+			MakeParents: a.info.MakeParents,
+			Owner:       a.info.ChownOpt.marshal(base),
+			Timestamp:   marshalTime(a.info.CreatedTime),
+		},
+	}
+}
+
+type MkdirOption interface {
+	SetMkdirOption(*MkdirInfo)
+}
+
+type ChownOption interface {
+	MkdirOption
+	MkfileOption
+	CopyOption
+}
+
+type mkdirOptionFunc func(*MkdirInfo)
+
+func (fn mkdirOptionFunc) SetMkdirOption(mi *MkdirInfo) {
+	fn(mi)
+}
+
+var _ MkdirOption = &MkdirInfo{}
+
+func WithParents(b bool) MkdirOption {
+	return mkdirOptionFunc(func(mi *MkdirInfo) {
+		mi.MakeParents = b
+	})
+}
+
+type MkdirInfo struct {
+	MakeParents bool
+	ChownOpt    *ChownOpt
+	CreatedTime *time.Time
+}
+
+func (mi *MkdirInfo) SetMkdirOption(mi2 *MkdirInfo) {
+	*mi2 = *mi
+}
+
+func WithUser(name string) ChownOption {
+	opt := ChownOpt{}
+
+	parts := strings.SplitN(name, ":", 2)
+	for i, v := range parts {
+		switch i {
+		case 0:
+			uid, err := parseUID(v)
+			if err != nil {
+				opt.User = &UserOpt{Name: v}
+			} else {
+				opt.User = &UserOpt{UID: uid}
+			}
+		case 1:
+			gid, err := parseUID(v)
+			if err != nil {
+				opt.Group = &UserOpt{Name: v}
+			} else {
+				opt.Group = &UserOpt{UID: gid}
+			}
+		}
+	}
+
+	return opt
+}
+
+func parseUID(str string) (int, error) {
+	if str == "root" {
+		return 0, nil
+	}
+	uid, err := strconv.ParseInt(str, 10, 32)
+	if err != nil {
+		return 0, err
+	}
+	return int(uid), nil
+}
+
+func WithUIDGID(uid, gid int) ChownOption {
+	return ChownOpt{
+		User:  &UserOpt{UID: uid},
+		Group: &UserOpt{UID: gid},
+	}
+}
+
+type ChownOpt struct {
+	User  *UserOpt
+	Group *UserOpt
+}
+
+func (co ChownOpt) SetMkdirOption(mi *MkdirInfo) {
+	mi.ChownOpt = &co
+}
+func (co ChownOpt) SetMkfileOption(mi *MkfileInfo) {
+	mi.ChownOpt = &co
+}
+func (co ChownOpt) SetCopyOption(mi *CopyInfo) {
+	mi.ChownOpt = &co
+}
+
+func (cp *ChownOpt) marshal(base pb.InputIndex) *pb.ChownOpt {
+	if cp == nil {
+		return nil
+	}
+	return &pb.ChownOpt{
+		User:  cp.User.marshal(base),
+		Group: cp.Group.marshal(base),
+	}
+}
+
+type UserOpt struct {
+	UID  int
+	Name string
+}
+
+func (up *UserOpt) marshal(base pb.InputIndex) *pb.UserOpt {
+	if up == nil {
+		return nil
+	}
+	if up.Name != "" {
+		return &pb.UserOpt{User: &pb.UserOpt_ByName{ByName: &pb.NamedUserOpt{
+			Name: up.Name, Input: base}}}
+	}
+	return &pb.UserOpt{User: &pb.UserOpt_ByID{ByID: uint32(up.UID)}}
+}
+
+func Mkfile(p string, m os.FileMode, dt []byte, opts ...MkfileOption) *FileAction {
+	var mi MkfileInfo
+	for _, o := range opts {
+		o.SetMkfileOption(&mi)
+	}
+
+	return &FileAction{
+		action: &fileActionMkfile{
+			file: p,
+			mode: m,
+			dt:   dt,
+			info: mi,
+		},
+	}
+}
+
+type MkfileOption interface {
+	SetMkfileOption(*MkfileInfo)
+}
+
+type MkfileInfo struct {
+	ChownOpt    *ChownOpt
+	CreatedTime *time.Time
+}
+
+func (mi *MkfileInfo) SetMkfileOption(mi2 *MkfileInfo) {
+	*mi2 = *mi
+}
+
+var _ MkfileOption = &MkfileInfo{}
+
+type fileActionMkfile struct {
+	file string
+	mode os.FileMode
+	dt   []byte
+	info MkfileInfo
+}
+
+func (a *fileActionMkfile) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
+	return &pb.FileAction_Mkfile{
+		Mkfile: &pb.FileActionMkFile{
+			Path:      normalizePath(parent, a.file, false),
+			Mode:      int32(a.mode & 0777),
+			Data:      a.dt,
+			Owner:     a.info.ChownOpt.marshal(base),
+			Timestamp: marshalTime(a.info.CreatedTime),
+		},
+	}
+}
+
+func Rm(p string, opts ...RmOption) *FileAction {
+	var mi RmInfo
+	for _, o := range opts {
+		o.SetRmOption(&mi)
+	}
+
+	return &FileAction{
+		action: &fileActionRm{
+			file: p,
+			info: mi,
+		},
+	}
+}
+
+type RmOption interface {
+	SetRmOption(*RmInfo)
+}
+
+type rmOptionFunc func(*RmInfo)
+
+func (fn rmOptionFunc) SetRmOption(mi *RmInfo) {
+	fn(mi)
+}
+
+type RmInfo struct {
+	AllowNotFound bool
+	AllowWildcard bool
+}
+
+func (mi *RmInfo) SetRmOption(mi2 *RmInfo) {
+	*mi2 = *mi
+}
+
+var _ RmOption = &RmInfo{}
+
+func WithAllowNotFound(b bool) RmOption {
+	return rmOptionFunc(func(mi *RmInfo) {
+		mi.AllowNotFound = b
+	})
+}
+
+func WithAllowWildcard(b bool) RmOption {
+	return rmOptionFunc(func(mi *RmInfo) {
+		mi.AllowWildcard = b
+	})
+}
+
+type fileActionRm struct {
+	file string
+	info RmInfo
+}
+
+func (a *fileActionRm) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
+	return &pb.FileAction_Rm{
+		Rm: &pb.FileActionRm{
+			Path:          normalizePath(parent, a.file, false),
+			AllowNotFound: a.info.AllowNotFound,
+			AllowWildcard: a.info.AllowWildcard,
+		},
+	}
+}
+
+func Copy(input CopyInput, src, dest string, opts ...CopyOption) *FileAction {
+	var state *State
+	var fas *fileActionWithState
+	var err error
+	if st, ok := input.(State); ok {
+		state = &st
+	} else if v, ok := input.(*fileActionWithState); ok {
+		fas = v
+	} else {
+		err = errors.Errorf("invalid input type %T for copy", input)
+	}
+
+	var mi CopyInfo
+	for _, o := range opts {
+		o.SetCopyOption(&mi)
+	}
+
+	return &FileAction{
+		action: &fileActionCopy{
+			state: state,
+			fas:   fas,
+			src:   src,
+			dest:  dest,
+			info:  mi,
+		},
+		err: err,
+	}
+}
+
+type CopyOption interface {
+	SetCopyOption(*CopyInfo)
+}
+
+type CopyInfo struct {
+	Mode                *os.FileMode
+	FollowSymlinks      bool
+	CopyDirContentsOnly bool
+	AttemptUnpack       bool
+	CreateDestPath      bool
+	AllowWildcard       bool
+	AllowEmptyWildcard  bool
+	ChownOpt            *ChownOpt
+	CreatedTime         *time.Time
+}
+
+func (mi *CopyInfo) SetCopyOption(mi2 *CopyInfo) {
+	*mi2 = *mi
+}
+
+var _ CopyOption = &CopyInfo{}
+
+type fileActionCopy struct {
+	state *State
+	fas   *fileActionWithState
+	src   string
+	dest  string
+	info  CopyInfo
+}
+
+func (a *fileActionCopy) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
+	c := &pb.FileActionCopy{
+		Src:                              a.sourcePath(),
+		Dest:                             normalizePath(parent, a.dest, true),
+		Owner:                            a.info.ChownOpt.marshal(base),
+		AllowWildcard:                    a.info.AllowWildcard,
+		AllowEmptyWildcard:               a.info.AllowEmptyWildcard,
+		FollowSymlink:                    a.info.FollowSymlinks,
+		DirCopyContents:                  a.info.CopyDirContentsOnly,
+		AttemptUnpackDockerCompatibility: a.info.AttemptUnpack,
+		CreateDestPath:                   a.info.CreateDestPath,
+		Timestamp:                        marshalTime(a.info.CreatedTime),
+	}
+	if a.info.Mode != nil {
+		c.Mode = int32(*a.info.Mode)
+	} else {
+		c.Mode = -1
+	}
+	return &pb.FileAction_Copy{
+		Copy: c,
+	}
+}
+
+func (c *fileActionCopy) sourcePath() string {
+	p := path.Clean(c.src)
+	if !path.IsAbs(p) {
+		if c.state != nil {
+			p = path.Join("/", c.state.GetDir(), p)
+		} else if c.fas != nil {
+			p = path.Join("/", c.fas.state.GetDir(), p)
+		}
+	}
+	return p
+}
+
+type CreatedTime time.Time
+
+func WithCreatedTime(t time.Time) CreatedTime {
+	return CreatedTime(t)
+}
+
+func (c CreatedTime) SetMkdirOption(mi *MkdirInfo) {
+	mi.CreatedTime = (*time.Time)(&c)
+}
+
+func (c CreatedTime) SetMkfileOption(mi *MkfileInfo) {
+	mi.CreatedTime = (*time.Time)(&c)
+}
+
+func (c CreatedTime) SetCopyOption(mi *CopyInfo) {
+	mi.CreatedTime = (*time.Time)(&c)
+}
+
+func marshalTime(t *time.Time) int64 {
+	if t == nil {
+		return -1
+	}
+	return t.UnixNano()
+}
+
+type FileOp struct {
+	MarshalCache
+	action *FileAction
+	output Output
+
+	constraints Constraints
+	isValidated bool
+}
+
+func (f *FileOp) Validate() error {
+	if f.isValidated {
+		return nil
+	}
+	if f.action == nil {
+		return errors.Errorf("action is required")
+	}
+	f.isValidated = true
+	return nil
+}
+
+type marshalState struct {
+	visited map[*FileAction]*fileActionState
+	inputs  []*pb.Input
+	actions []*fileActionState
+}
+
+func newMarshalState() *marshalState {
+	return &marshalState{
+		visited: map[*FileAction]*fileActionState{},
+	}
+}
+
+type fileActionState struct {
+	base           pb.InputIndex
+	input          pb.InputIndex
+	inputRelative  *int
+	input2         pb.InputIndex
+	input2Relative *int
+	target         int
+	action         subAction
+	fa             *FileAction
+}
+
+func (ms *marshalState) addInput(st *fileActionState, c *Constraints, o Output) (pb.InputIndex, error) {
+	inp, err := o.ToInput(c)
+	if err != nil {
+		return 0, err
+	}
+	for i, inp2 := range ms.inputs {
+		if *inp == *inp2 {
+			return pb.InputIndex(i), nil
+		}
+	}
+	i := pb.InputIndex(len(ms.inputs))
+	ms.inputs = append(ms.inputs, inp)
+	return i, nil
+}
+
+func (ms *marshalState) add(fa *FileAction, c *Constraints) (*fileActionState, error) {
+	if st, ok := ms.visited[fa]; ok {
+		return st, nil
+	}
+
+	if fa.err != nil {
+		return nil, fa.err
+	}
+
+	var prevState *fileActionState
+	if parent := fa.prev; parent != nil {
+		var err error
+		prevState, err = ms.add(parent, c)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	st := &fileActionState{
+		action: fa.action,
+		input:  -1,
+		input2: -1,
+		base:   -1,
+		fa:     fa,
+	}
+
+	if source := fa.state.Output(); source != nil {
+		inp, err := ms.addInput(st, c, source)
+		if err != nil {
+			return nil, err
+		}
+		st.base = inp
+	}
+
+	if fa.prev == nil {
+		st.input = st.base
+	} else {
+		st.inputRelative = &prevState.target
+	}
+
+	if a, ok := fa.action.(*fileActionCopy); ok {
+		if a.state != nil {
+			if out := a.state.Output(); out != nil {
+				inp, err := ms.addInput(st, c, out)
+				if err != nil {
+					return nil, err
+				}
+				st.input2 = inp
+			}
+		} else if a.fas != nil {
+			src, err := ms.add(a.fas.FileAction, c)
+			if err != nil {
+				return nil, err
+			}
+			st.input2Relative = &src.target
+		} else {
+			return nil, errors.Errorf("invalid empty source for copy")
+		}
+	}
+
+	st.target = len(ms.actions)
+
+	ms.visited[fa] = st
+	ms.actions = append(ms.actions, st)
+
+	return st, nil
+}
+
+func (f *FileOp) Marshal(c *Constraints) (digest.Digest, []byte, *pb.OpMetadata, error) {
+	if f.Cached(c) {
+		return f.Load()
+	}
+	if err := f.Validate(); err != nil {
+		return "", nil, nil, err
+	}
+
+	addCap(&f.constraints, pb.CapFileBase)
+
+	pfo := &pb.FileOp{}
+
+	pop, md := MarshalConstraints(c, &f.constraints)
+	pop.Op = &pb.Op_File{
+		File: pfo,
+	}
+
+	state := newMarshalState()
+	_, err := state.add(f.action, c)
+	if err != nil {
+		return "", nil, nil, err
+	}
+	pop.Inputs = state.inputs
+
+	for i, st := range state.actions {
+		output := pb.OutputIndex(-1)
+		if i+1 == len(state.actions) {
+			output = 0
+		}
+
+		var parent string
+		if st.fa.state != nil {
+			parent = st.fa.state.GetDir()
+		}
+
+		pfo.Actions = append(pfo.Actions, &pb.FileAction{
+			Input:          getIndex(st.input, len(state.inputs), st.inputRelative),
+			SecondaryInput: getIndex(st.input2, len(state.inputs), st.input2Relative),
+			Output:         output,
+			Action:         st.action.toProtoAction(parent, st.base),
+		})
+	}
+
+	dt, err := pop.Marshal()
+	if err != nil {
+		return "", nil, nil, err
+	}
+	f.Store(dt, md, c)
+	return f.Load()
+}
+
+func normalizePath(parent, p string, keepSlash bool) string {
+	origPath := p
+	p = path.Clean(p)
+	if !path.IsAbs(p) {
+		p = path.Join("/", parent, p)
+	}
+	if keepSlash {
+		if strings.HasSuffix(origPath, "/") && !strings.HasSuffix(p, "/") {
+			p += "/"
+		} else if strings.HasSuffix(origPath, "/.") {
+			if p != "/" {
+				p += "/"
+			}
+			p += "."
+		}
+	}
+	return p
+}
+
+func (f *FileOp) Output() Output {
+	return f.output
+}
+
+func (f *FileOp) Inputs() (inputs []Output) {
+	mm := map[Output]struct{}{}
+
+	f.action.allOutputs(mm)
+
+	for o := range mm {
+		inputs = append(inputs, o)
+	}
+	return inputs
+}
+
+func getIndex(input pb.InputIndex, len int, relative *int) pb.InputIndex {
+	if relative != nil {
+		return pb.InputIndex(len + *relative)
+	}
+	return input
+}

+ 11 - 0
vendor/github.com/moby/buildkit/client/llb/state.go

@@ -229,6 +229,15 @@ func (s State) Run(ro ...RunOption) ExecState {
 	}
 	}
 }
 }
 
 
+func (s State) File(a *FileAction, opts ...ConstraintsOpt) State {
+	var c Constraints
+	for _, o := range opts {
+		o.SetConstraintsOption(&c)
+	}
+
+	return s.WithOutput(NewFileOp(s, a, c).Output())
+}
+
 func (s State) AddEnv(key, value string) State {
 func (s State) AddEnv(key, value string) State {
 	return s.AddEnvf(key, value)
 	return s.AddEnvf(key, value)
 }
 }
@@ -295,6 +304,8 @@ func (s State) AddExtraHost(host string, ip net.IP) State {
 	return extraHost(host, ip)(s)
 	return extraHost(host, ip)(s)
 }
 }
 
 
+func (s State) isFileOpCopyInput() {}
+
 type output struct {
 type output struct {
 	vertex   Vertex
 	vertex   Vertex
 	getIndex func() (pb.OutputIndex, error)
 	getIndex func() (pb.OutputIndex, error)

+ 1 - 1
vendor/github.com/moby/buildkit/client/solve.go

@@ -298,7 +298,7 @@ func prepareSyncedDirs(def *llb.Definition, localDirs map[string]string) ([]file
 			return nil, errors.Errorf("%s not a directory", d)
 			return nil, errors.Errorf("%s not a directory", d)
 		}
 		}
 	}
 	}
-	resetUIDAndGID := func(st *fstypes.Stat) bool {
+	resetUIDAndGID := func(p string, st *fstypes.Stat) bool {
 		st.Uid = 0
 		st.Uid = 0
 		st.Gid = 0
 		st.Gid = 0
 		return true
 		return true

+ 122 - 6
vendor/github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb/convert.go

@@ -151,6 +151,10 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
 			switch cmd.(type) {
 			switch cmd.(type) {
 			case *instructions.AddCommand, *instructions.CopyCommand, *instructions.RunCommand:
 			case *instructions.AddCommand, *instructions.CopyCommand, *instructions.RunCommand:
 				total++
 				total++
+			case *instructions.WorkdirCommand:
+				if useFileOp(opt.BuildArgs, opt.LLBCaps) {
+					total++
+				}
 			}
 			}
 		}
 		}
 		ds.cmdTotal = total
 		ds.cmdTotal = total
@@ -307,7 +311,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
 			d.state = d.state.AddEnv(k, v)
 			d.state = d.state.AddEnv(k, v)
 		}
 		}
 		if d.image.Config.WorkingDir != "" {
 		if d.image.Config.WorkingDir != "" {
-			if err = dispatchWorkdir(d, &instructions.WorkdirCommand{Path: d.image.Config.WorkingDir}, false); err != nil {
+			if err = dispatchWorkdir(d, &instructions.WorkdirCommand{Path: d.image.Config.WorkingDir}, false, nil); err != nil {
 				return nil, nil, err
 				return nil, nil, err
 			}
 			}
 		}
 		}
@@ -468,9 +472,9 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
 	case *instructions.RunCommand:
 	case *instructions.RunCommand:
 		err = dispatchRun(d, c, opt.proxyEnv, cmd.sources, opt)
 		err = dispatchRun(d, c, opt.proxyEnv, cmd.sources, opt)
 	case *instructions.WorkdirCommand:
 	case *instructions.WorkdirCommand:
-		err = dispatchWorkdir(d, c, true)
+		err = dispatchWorkdir(d, c, true, &opt)
 	case *instructions.AddCommand:
 	case *instructions.AddCommand:
-		err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, "", opt)
+		err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, c.Chown, opt)
 		if err == nil {
 		if err == nil {
 			for _, src := range c.Sources() {
 			for _, src := range c.Sources() {
 				if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
 				if !strings.HasPrefix(src, "http://") && !strings.HasPrefix(src, "https://") {
@@ -648,7 +652,7 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
 	return commitToHistory(&d.image, "RUN "+runCommandString(args, d.buildArgs), true, &d.state)
 	return commitToHistory(&d.image, "RUN "+runCommandString(args, d.buildArgs), true, &d.state)
 }
 }
 
 
-func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bool) error {
+func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bool, opt *dispatchOpt) error {
 	d.state = d.state.Dir(c.Path)
 	d.state = d.state.Dir(c.Path)
 	wd := c.Path
 	wd := c.Path
 	if !path.IsAbs(c.Path) {
 	if !path.IsAbs(c.Path) {
@@ -656,13 +660,115 @@ func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bo
 	}
 	}
 	d.image.Config.WorkingDir = wd
 	d.image.Config.WorkingDir = wd
 	if commit {
 	if commit {
-		return commitToHistory(&d.image, "WORKDIR "+wd, false, nil)
+		withLayer := false
+		if wd != "/" && opt != nil && useFileOp(opt.buildArgValues, opt.llbCaps) {
+			mkdirOpt := []llb.MkdirOption{llb.WithParents(true)}
+			if user := d.image.Config.User; user != "" {
+				mkdirOpt = append(mkdirOpt, llb.WithUser(user))
+			}
+			platform := opt.targetPlatform
+			if d.platform != nil {
+				platform = *d.platform
+			}
+			d.state = d.state.File(llb.Mkdir(wd, 0755, mkdirOpt...), llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(opt.shlex, c.String(), d.state.Env())), d.prefixPlatform, &platform)))
+			withLayer = true
+		}
+		return commitToHistory(&d.image, "WORKDIR "+wd, withLayer, nil)
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
+func dispatchCopyFileOp(d *dispatchState, c instructions.SourcesAndDest, sourceState llb.State, isAddCommand bool, cmdToPrint fmt.Stringer, chown string, opt dispatchOpt) error {
+	dest := path.Join("/", pathRelativeToWorkingDir(d.state, c.Dest()))
+	if c.Dest() == "." || c.Dest() == "" || c.Dest()[len(c.Dest())-1] == filepath.Separator {
+		dest += string(filepath.Separator)
+	}
+
+	var copyOpt []llb.CopyOption
+
+	if chown != "" {
+		copyOpt = append(copyOpt, llb.WithUser(chown))
+	}
+
+	commitMessage := bytes.NewBufferString("")
+	if isAddCommand {
+		commitMessage.WriteString("ADD")
+	} else {
+		commitMessage.WriteString("COPY")
+	}
+
+	var a *llb.FileAction
+
+	for _, src := range c.Sources() {
+		commitMessage.WriteString(" " + src)
+		if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") {
+			if !isAddCommand {
+				return errors.New("source can't be a URL for COPY")
+			}
+
+			// Resources from remote URLs are not decompressed.
+			// https://docs.docker.com/engine/reference/builder/#add
+			//
+			// Note: mixing up remote archives and local archives in a single ADD instruction
+			// would result in undefined behavior: https://github.com/moby/buildkit/pull/387#discussion_r189494717
+			u, err := url.Parse(src)
+			f := "__unnamed__"
+			if err == nil {
+				if base := path.Base(u.Path); base != "." && base != "/" {
+					f = base
+				}
+			}
+
+			st := llb.HTTP(src, llb.Filename(f), dfCmd(c))
+
+			opts := append([]llb.CopyOption{&llb.CopyInfo{
+				CreateDestPath: true,
+			}}, copyOpt...)
+
+			if a == nil {
+				a = llb.Copy(st, f, dest, opts...)
+			} else {
+				a = a.Copy(st, f, dest, opts...)
+			}
+		} else {
+			opts := append([]llb.CopyOption{&llb.CopyInfo{
+				FollowSymlinks:      true,
+				CopyDirContentsOnly: true,
+				AttemptUnpack:       isAddCommand,
+				CreateDestPath:      true,
+				AllowWildcard:       true,
+				AllowEmptyWildcard:  true,
+			}}, copyOpt...)
+
+			if a == nil {
+				a = llb.Copy(sourceState, src, dest, opts...)
+			} else {
+				a = a.Copy(sourceState, src, dest, opts...)
+			}
+		}
+	}
+
+	commitMessage.WriteString(" " + c.Dest())
+
+	platform := opt.targetPlatform
+	if d.platform != nil {
+		platform = *d.platform
+	}
+
+	fileOpt := []llb.ConstraintsOpt{llb.WithCustomName(prefixCommand(d, uppercaseCmd(processCmdEnv(opt.shlex, cmdToPrint.String(), d.state.Env())), d.prefixPlatform, &platform))}
+	if d.ignoreCache {
+		fileOpt = append(fileOpt, llb.IgnoreCache)
+	}
+
+	d.state = d.state.File(a, fileOpt...)
+	return commitToHistory(&d.image, commitMessage.String(), true, &d.state)
+}
+
 func dispatchCopy(d *dispatchState, c instructions.SourcesAndDest, sourceState llb.State, isAddCommand bool, cmdToPrint fmt.Stringer, chown string, opt dispatchOpt) error {
 func dispatchCopy(d *dispatchState, c instructions.SourcesAndDest, sourceState llb.State, isAddCommand bool, cmdToPrint fmt.Stringer, chown string, opt dispatchOpt) error {
-	// TODO: this should use CopyOp instead. Current implementation is inefficient
+	if useFileOp(opt.buildArgValues, opt.llbCaps) {
+		return dispatchCopyFileOp(d, c, sourceState, isAddCommand, cmdToPrint, chown, opt)
+	}
+
 	img := llb.Image(opt.copyImage, llb.MarkImageInternal, llb.Platform(opt.buildPlatforms[0]), WithInternalName("helper image for file operations"))
 	img := llb.Image(opt.copyImage, llb.MarkImageInternal, llb.Platform(opt.buildPlatforms[0]), WithInternalName("helper image for file operations"))
 
 
 	dest := path.Join(".", pathRelativeToWorkingDir(d.state, c.Dest()))
 	dest := path.Join(".", pathRelativeToWorkingDir(d.state, c.Dest()))
@@ -1176,3 +1282,13 @@ func prefixCommand(ds *dispatchState, str string, prefixPlatform bool, platform
 	out += fmt.Sprintf("%d/%d] ", ds.cmdIndex, ds.cmdTotal)
 	out += fmt.Sprintf("%d/%d] ", ds.cmdIndex, ds.cmdTotal)
 	return out + str
 	return out + str
 }
 }
+
+func useFileOp(args map[string]string, caps *apicaps.CapSet) bool {
+	enabled := true
+	if v, ok := args["BUILDKIT_DISABLE_FILEOP"]; ok {
+		if b, err := strconv.ParseBool(v); err == nil {
+			enabled = !b
+		}
+	}
+	return enabled && caps != nil && caps.Supports(pb.CapFileBase) == nil
+}

+ 2 - 2
vendor/github.com/moby/buildkit/session/filesync/diffcopy.go

@@ -82,10 +82,10 @@ func syncTargetDiffCopy(ds grpc.Stream, dest string) error {
 	}
 	}
 	return fsutil.Receive(ds.Context(), ds, dest, fsutil.ReceiveOpt{
 	return fsutil.Receive(ds.Context(), ds, dest, fsutil.ReceiveOpt{
 		Merge: true,
 		Merge: true,
-		Filter: func() func(*fstypes.Stat) bool {
+		Filter: func() func(string, *fstypes.Stat) bool {
 			uid := os.Getuid()
 			uid := os.Getuid()
 			gid := os.Getgid()
 			gid := os.Getgid()
-			return func(st *fstypes.Stat) bool {
+			return func(p string, st *fstypes.Stat) bool {
 				st.Uid = uint32(uid)
 				st.Uid = uint32(uid)
 				st.Gid = uint32(gid)
 				st.Gid = uint32(gid)
 				return true
 				return true

+ 1 - 1
vendor/github.com/moby/buildkit/session/filesync/filesync.go

@@ -35,7 +35,7 @@ type SyncedDir struct {
 	Name     string
 	Name     string
 	Dir      string
 	Dir      string
 	Excludes []string
 	Excludes []string
-	Map      func(*fstypes.Stat) bool
+	Map      func(string, *fstypes.Stat) bool
 }
 }
 
 
 // NewFSSyncProvider creates a new provider for sending files from client
 // NewFSSyncProvider creates a new provider for sending files from client

+ 266 - 0
vendor/github.com/moby/buildkit/solver/llbsolver/file/backend.go

@@ -0,0 +1,266 @@
+package file
+
+import (
+	"context"
+	"io/ioutil"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/containerd/continuity/fs"
+	"github.com/moby/buildkit/snapshot"
+	"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
+	"github.com/moby/buildkit/solver/pb"
+	"github.com/pkg/errors"
+	copy "github.com/tonistiigi/fsutil/copy"
+)
+
+func timestampToTime(ts int64) *time.Time {
+	if ts == -1 {
+		return nil
+	}
+	tm := time.Unix(ts/1e9, ts%1e9)
+	return &tm
+}
+
+func mkdir(ctx context.Context, d string, action pb.FileActionMkDir, user *copy.ChownOpt) error {
+	p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
+	if err != nil {
+		return err
+	}
+
+	if action.MakeParents {
+		if err := copy.MkdirAll(p, os.FileMode(action.Mode)&0777, user, timestampToTime(action.Timestamp)); err != nil {
+			return err
+		}
+	} else {
+		if err := os.Mkdir(p, os.FileMode(action.Mode)&0777); err != nil {
+			if os.IsExist(err) {
+				return nil
+			}
+			return err
+		}
+		if err := copy.Chown(p, user); err != nil {
+			return err
+		}
+		if err := copy.Utimes(p, timestampToTime(action.Timestamp)); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func mkfile(ctx context.Context, d string, action pb.FileActionMkFile, user *copy.ChownOpt) error {
+	p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
+	if err != nil {
+		return err
+	}
+
+	if err := ioutil.WriteFile(p, action.Data, os.FileMode(action.Mode)&0777); err != nil {
+		return err
+	}
+
+	if err := copy.Chown(p, user); err != nil {
+		return err
+	}
+
+	if err := copy.Utimes(p, timestampToTime(action.Timestamp)); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func rm(ctx context.Context, d string, action pb.FileActionRm) error {
+	p, err := fs.RootPath(d, filepath.Join(filepath.Join("/", action.Path)))
+	if err != nil {
+		return err
+	}
+
+	if err := os.RemoveAll(p); err != nil {
+		if os.IsNotExist(errors.Cause(err)) && action.AllowNotFound {
+			return nil
+		}
+		return err
+	}
+
+	return nil
+}
+
+func docopy(ctx context.Context, src, dest string, action pb.FileActionCopy, u *copy.ChownOpt) error {
+	srcPath := cleanPath(action.Src)
+	destPath := cleanPath(action.Dest)
+
+	if !action.CreateDestPath {
+		p, err := fs.RootPath(dest, filepath.Join(filepath.Join("/", action.Dest)))
+		if err != nil {
+			return err
+		}
+		if _, err := os.Lstat(filepath.Dir(p)); err != nil {
+			return errors.Wrapf(err, "failed to stat %s", action.Dest)
+		}
+	}
+
+	xattrErrorHandler := func(dst, src, key string, err error) error {
+		log.Println(err)
+		return nil
+	}
+
+	opt := []copy.Opt{
+		func(ci *copy.CopyInfo) {
+			ci.Chown = u
+			ci.Utime = timestampToTime(action.Timestamp)
+			if m := int(action.Mode); m != -1 {
+				ci.Mode = &m
+			}
+			ci.CopyDirContents = action.DirCopyContents
+			ci.FollowLinks = action.FollowSymlink
+		},
+		copy.WithXAttrErrorHandler(xattrErrorHandler),
+	}
+
+	if !action.AllowWildcard {
+		if action.AttemptUnpackDockerCompatibility {
+			if ok, err := unpack(ctx, src, srcPath, dest, destPath, u, timestampToTime(action.Timestamp)); err != nil {
+				return err
+			} else if ok {
+				return nil
+			}
+		}
+		return copy.Copy(ctx, src, srcPath, dest, destPath, opt...)
+	}
+
+	m, err := copy.ResolveWildcards(src, srcPath, action.FollowSymlink)
+	if err != nil {
+		return err
+	}
+
+	if len(m) == 0 {
+		if action.AllowEmptyWildcard {
+			return nil
+		}
+		return errors.Errorf("%s not found", srcPath)
+	}
+
+	for _, s := range m {
+		if action.AttemptUnpackDockerCompatibility {
+			if ok, err := unpack(ctx, src, s, dest, destPath, u, timestampToTime(action.Timestamp)); err != nil {
+				return err
+			} else if ok {
+				continue
+			}
+		}
+		if err := copy.Copy(ctx, src, s, dest, destPath, opt...); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func cleanPath(s string) string {
+	s2 := filepath.Join("/", s)
+	if strings.HasSuffix(s, "/.") {
+		if s2 != "/" {
+			s2 += "/"
+		}
+		s2 += "."
+	} else if strings.HasSuffix(s, "/") && s2 != "/" {
+		s2 += "/"
+	}
+	return s2
+}
+
+type Backend struct {
+}
+
+func (fb *Backend) Mkdir(ctx context.Context, m, user, group fileoptypes.Mount, action pb.FileActionMkDir) error {
+	mnt, ok := m.(*Mount)
+	if !ok {
+		return errors.Errorf("invalid mount type %T", m)
+	}
+
+	lm := snapshot.LocalMounter(mnt.m)
+	dir, err := lm.Mount()
+	if err != nil {
+		return err
+	}
+	defer lm.Unmount()
+
+	u, err := readUser(action.Owner, user, group)
+	if err != nil {
+		return err
+	}
+
+	return mkdir(ctx, dir, action, u)
+}
+
+func (fb *Backend) Mkfile(ctx context.Context, m, user, group fileoptypes.Mount, action pb.FileActionMkFile) error {
+	mnt, ok := m.(*Mount)
+	if !ok {
+		return errors.Errorf("invalid mount type %T", m)
+	}
+
+	lm := snapshot.LocalMounter(mnt.m)
+	dir, err := lm.Mount()
+	if err != nil {
+		return err
+	}
+	defer lm.Unmount()
+
+	u, err := readUser(action.Owner, user, group)
+	if err != nil {
+		return err
+	}
+
+	return mkfile(ctx, dir, action, u)
+}
+func (fb *Backend) Rm(ctx context.Context, m fileoptypes.Mount, action pb.FileActionRm) error {
+	mnt, ok := m.(*Mount)
+	if !ok {
+		return errors.Errorf("invalid mount type %T", m)
+	}
+
+	lm := snapshot.LocalMounter(mnt.m)
+	dir, err := lm.Mount()
+	if err != nil {
+		return err
+	}
+	defer lm.Unmount()
+
+	return rm(ctx, dir, action)
+}
+func (fb *Backend) Copy(ctx context.Context, m1, m2, user, group fileoptypes.Mount, action pb.FileActionCopy) error {
+	mnt1, ok := m1.(*Mount)
+	if !ok {
+		return errors.Errorf("invalid mount type %T", m1)
+	}
+	mnt2, ok := m2.(*Mount)
+	if !ok {
+		return errors.Errorf("invalid mount type %T", m2)
+	}
+
+	lm := snapshot.LocalMounter(mnt1.m)
+	src, err := lm.Mount()
+	if err != nil {
+		return err
+	}
+	defer lm.Unmount()
+
+	lm2 := snapshot.LocalMounter(mnt2.m)
+	dest, err := lm2.Mount()
+	if err != nil {
+		return err
+	}
+	defer lm2.Unmount()
+
+	u, err := readUser(action.Owner, user, group)
+	if err != nil {
+		return err
+	}
+
+	return docopy(ctx, src, dest, action, u)
+}

+ 71 - 0
vendor/github.com/moby/buildkit/solver/llbsolver/file/refmanager.go

@@ -0,0 +1,71 @@
+package file
+
+import (
+	"context"
+
+	"github.com/moby/buildkit/cache"
+	"github.com/moby/buildkit/snapshot"
+	"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
+	"github.com/pkg/errors"
+)
+
+func NewRefManager(cm cache.Manager) *RefManager {
+	return &RefManager{cm: cm}
+}
+
+type RefManager struct {
+	cm cache.Manager
+}
+
+func (rm *RefManager) Prepare(ctx context.Context, ref fileoptypes.Ref, readonly bool) (fileoptypes.Mount, error) {
+	ir, ok := ref.(cache.ImmutableRef)
+	if !ok && ref != nil {
+		return nil, errors.Errorf("invalid ref type: %T", ref)
+	}
+
+	if ir != nil && readonly {
+		m, err := ir.Mount(ctx, readonly)
+		if err != nil {
+			return nil, err
+		}
+		return &Mount{m: m}, nil
+	}
+
+	mr, err := rm.cm.New(ctx, ir, cache.WithDescription("fileop target"), cache.CachePolicyRetain)
+	if err != nil {
+		return nil, err
+	}
+	m, err := mr.Mount(ctx, readonly)
+	if err != nil {
+		return nil, err
+	}
+	return &Mount{m: m, mr: mr}, nil
+}
+
+func (rm *RefManager) Commit(ctx context.Context, mount fileoptypes.Mount) (fileoptypes.Ref, error) {
+	m, ok := mount.(*Mount)
+	if !ok {
+		return nil, errors.Errorf("invalid mount type %T", mount)
+	}
+	if err := m.m.Release(); err != nil {
+		return nil, err
+	}
+	if m.mr == nil {
+		return nil, errors.Errorf("invalid mount without active ref for commit")
+	}
+	return m.mr.Commit(ctx)
+}
+
+type Mount struct {
+	m  snapshot.Mountable
+	mr cache.MutableRef
+}
+
+func (m *Mount) Release(ctx context.Context) error {
+	m.m.Release()
+	if m.mr != nil {
+		return m.mr.Release(ctx)
+	}
+	return nil
+}
+func (m *Mount) IsFileOpMount() {}

+ 61 - 0
vendor/github.com/moby/buildkit/solver/llbsolver/file/unpack.go

@@ -0,0 +1,61 @@
+package file
+
+import (
+	"archive/tar"
+	"context"
+	"os"
+	"time"
+
+	"github.com/containerd/continuity/fs"
+	"github.com/docker/docker/pkg/archive"
+	"github.com/docker/docker/pkg/chrootarchive"
+	copy "github.com/tonistiigi/fsutil/copy"
+)
+
+func unpack(ctx context.Context, srcRoot string, src string, destRoot string, dest string, user *copy.ChownOpt, tm *time.Time) (bool, error) {
+	src, err := fs.RootPath(srcRoot, src)
+	if err != nil {
+		return false, err
+	}
+	if !isArchivePath(src) {
+		return false, nil
+	}
+
+	dest, err = fs.RootPath(destRoot, dest)
+	if err != nil {
+		return false, err
+	}
+	if err := copy.MkdirAll(dest, 0755, user, tm); err != nil {
+		return false, err
+	}
+
+	file, err := os.Open(src)
+	if err != nil {
+		return false, err
+	}
+	defer file.Close()
+
+	return true, chrootarchive.Untar(file, dest, nil)
+}
+
+func isArchivePath(path string) bool {
+	fi, err := os.Lstat(path)
+	if err != nil {
+		return false
+	}
+	if fi.Mode()&os.ModeType != 0 {
+		return false
+	}
+	file, err := os.Open(path)
+	if err != nil {
+		return false
+	}
+	defer file.Close()
+	rdr, err := archive.DecompressStream(file)
+	if err != nil {
+		return false
+	}
+	r := tar.NewReader(rdr)
+	_, err = r.Next()
+	return err == nil
+}

+ 119 - 0
vendor/github.com/moby/buildkit/solver/llbsolver/file/user_linux.go

@@ -0,0 +1,119 @@
+package file
+
+import (
+	"os"
+
+	"github.com/containerd/continuity/fs"
+	"github.com/moby/buildkit/snapshot"
+	"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
+	"github.com/moby/buildkit/solver/pb"
+	"github.com/opencontainers/runc/libcontainer/user"
+	"github.com/pkg/errors"
+	copy "github.com/tonistiigi/fsutil/copy"
+)
+
+func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*copy.ChownOpt, error) {
+	if chopt == nil {
+		return nil, nil
+	}
+	var us copy.ChownOpt
+	if chopt.User != nil {
+		switch u := chopt.User.User.(type) {
+		case *pb.UserOpt_ByName:
+			if mu == nil {
+				return nil, errors.Errorf("invalid missing user mount")
+			}
+			mmu, ok := mu.(*Mount)
+			if !ok {
+				return nil, errors.Errorf("invalid mount type %T", mu)
+			}
+			lm := snapshot.LocalMounter(mmu.m)
+			dir, err := lm.Mount()
+			if err != nil {
+				return nil, err
+			}
+			defer lm.Unmount()
+
+			passwdPath, err := user.GetPasswdPath()
+			if err != nil {
+				return nil, err
+			}
+
+			passwdPath, err = fs.RootPath(dir, passwdPath)
+			if err != nil {
+				return nil, err
+			}
+
+			ufile, err := os.Open(passwdPath)
+			if err != nil {
+				return nil, err
+			}
+			defer ufile.Close()
+
+			users, err := user.ParsePasswdFilter(ufile, func(uu user.User) bool {
+				return uu.Name == u.ByName.Name
+			})
+			if err != nil {
+				return nil, err
+			}
+
+			if len(users) > 0 {
+				us.Uid = users[0].Uid
+				us.Gid = users[0].Gid
+			}
+		case *pb.UserOpt_ByID:
+			us.Uid = int(u.ByID)
+			us.Gid = int(u.ByID)
+		}
+	}
+
+	if chopt.Group != nil {
+		switch u := chopt.Group.User.(type) {
+		case *pb.UserOpt_ByName:
+			if mg == nil {
+				return nil, errors.Errorf("invalid missing group mount")
+			}
+			mmg, ok := mg.(*Mount)
+			if !ok {
+				return nil, errors.Errorf("invalid mount type %T", mg)
+			}
+			lm := snapshot.LocalMounter(mmg.m)
+			dir, err := lm.Mount()
+			if err != nil {
+				return nil, err
+			}
+			defer lm.Unmount()
+
+			groupPath, err := user.GetGroupPath()
+			if err != nil {
+				return nil, err
+			}
+
+			groupPath, err = fs.RootPath(dir, groupPath)
+			if err != nil {
+				return nil, err
+			}
+
+			gfile, err := os.Open(groupPath)
+			if err != nil {
+				return nil, err
+			}
+			defer gfile.Close()
+
+			groups, err := user.ParseGroupFilter(gfile, func(g user.Group) bool {
+				return g.Name == u.ByName.Name
+			})
+			if err != nil {
+				return nil, err
+			}
+
+			if len(groups) > 0 {
+				us.Gid = groups[0].Gid
+			}
+		case *pb.UserOpt_ByID:
+			us.Gid = int(u.ByID)
+		}
+	}
+
+	return &us, nil
+}

+ 14 - 0
vendor/github.com/moby/buildkit/solver/llbsolver/file/user_nolinux.go

@@ -0,0 +1,14 @@
+// +build !linux
+
+package file
+
+import (
+	"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
+	"github.com/moby/buildkit/solver/pb"
+	"github.com/pkg/errors"
+	copy "github.com/tonistiigi/fsutil/copy"
+)
+
+func readUser(chopt *pb.ChownOpt, mu, mg fileoptypes.Mount) (*copy.ChownOpt, error) {
+	return nil, errors.New("only implemented in linux")
+}

+ 9 - 1
vendor/github.com/moby/buildkit/solver/llbsolver/ops/exec.go

@@ -149,7 +149,7 @@ func (e *execOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, boo
 			cm.Deps[i].Selector = digest.FromBytes(bytes.Join(dgsts, []byte{0}))
 			cm.Deps[i].Selector = digest.FromBytes(bytes.Join(dgsts, []byte{0}))
 		}
 		}
 		if !dep.NoContentBasedHash {
 		if !dep.NoContentBasedHash {
-			cm.Deps[i].ComputeDigestFunc = llbsolver.NewContentHashFunc(dedupePaths(dep.Selectors))
+			cm.Deps[i].ComputeDigestFunc = llbsolver.NewContentHashFunc(toSelectors(dedupePaths(dep.Selectors)))
 		}
 		}
 	}
 	}
 
 
@@ -180,6 +180,14 @@ func dedupePaths(inp []string) []string {
 	return paths
 	return paths
 }
 }
 
 
+func toSelectors(p []string) []llbsolver.Selector {
+	sel := make([]llbsolver.Selector, 0, len(p))
+	for _, p := range p {
+		sel = append(sel, llbsolver.Selector{Path: p, FollowLinks: true})
+	}
+	return sel
+}
+
 type dep struct {
 type dep struct {
 	Selectors          []string
 	Selectors          []string
 	NoContentBasedHash bool
 	NoContentBasedHash bool

+ 580 - 0
vendor/github.com/moby/buildkit/solver/llbsolver/ops/file.go

@@ -0,0 +1,580 @@
+package ops
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"fmt"
+	"path"
+	"runtime"
+	"sort"
+	"sync"
+
+	"github.com/moby/buildkit/cache"
+	"github.com/moby/buildkit/cache/metadata"
+	"github.com/moby/buildkit/solver"
+	"github.com/moby/buildkit/solver/llbsolver"
+	"github.com/moby/buildkit/solver/llbsolver/file"
+	"github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes"
+	"github.com/moby/buildkit/solver/pb"
+	"github.com/moby/buildkit/util/flightcontrol"
+	"github.com/moby/buildkit/worker"
+	digest "github.com/opencontainers/go-digest"
+	"github.com/pkg/errors"
+	"golang.org/x/sync/errgroup"
+)
+
+const fileCacheType = "buildkit.file.v0"
+
+type fileOp struct {
+	op        *pb.FileOp
+	md        *metadata.Store
+	w         worker.Worker
+	solver    *FileOpSolver
+	numInputs int
+}
+
+func NewFileOp(v solver.Vertex, op *pb.Op_File, cm cache.Manager, md *metadata.Store, w worker.Worker) (solver.Op, error) {
+	return &fileOp{
+		op:        op.File,
+		md:        md,
+		numInputs: len(v.Inputs()),
+		w:         w,
+		solver:    NewFileOpSolver(&file.Backend{}, file.NewRefManager(cm)),
+	}, nil
+}
+
+func (f *fileOp) CacheMap(ctx context.Context, index int) (*solver.CacheMap, bool, error) {
+	selectors := map[int]map[llbsolver.Selector]struct{}{}
+	invalidSelectors := map[int]struct{}{}
+
+	actions := make([][]byte, 0, len(f.op.Actions))
+
+	markInvalid := func(idx pb.InputIndex) {
+		if idx != -1 {
+			invalidSelectors[int(idx)] = struct{}{}
+		}
+	}
+
+	for _, action := range f.op.Actions {
+		var dt []byte
+		var err error
+		switch a := action.Action.(type) {
+		case *pb.FileAction_Mkdir:
+			p := *a.Mkdir
+			markInvalid(action.Input)
+			processOwner(p.Owner, selectors)
+			dt, err = json.Marshal(p)
+			if err != nil {
+				return nil, false, err
+			}
+		case *pb.FileAction_Mkfile:
+			p := *a.Mkfile
+			markInvalid(action.Input)
+			processOwner(p.Owner, selectors)
+			dt, err = json.Marshal(p)
+			if err != nil {
+				return nil, false, err
+			}
+		case *pb.FileAction_Rm:
+			p := *a.Rm
+			markInvalid(action.Input)
+			dt, err = json.Marshal(p)
+			if err != nil {
+				return nil, false, err
+			}
+		case *pb.FileAction_Copy:
+			p := *a.Copy
+			markInvalid(action.Input)
+			processOwner(p.Owner, selectors)
+			if action.SecondaryInput != -1 && int(action.SecondaryInput) < f.numInputs {
+				addSelector(selectors, int(action.SecondaryInput), p.Src, p.AllowWildcard, p.FollowSymlink)
+				p.Src = path.Base(p.Src)
+			}
+			dt, err = json.Marshal(p)
+			if err != nil {
+				return nil, false, err
+			}
+		}
+
+		actions = append(actions, dt)
+	}
+
+	dt, err := json.Marshal(struct {
+		Type    string
+		Actions [][]byte
+	}{
+		Type:    fileCacheType,
+		Actions: actions,
+	})
+	if err != nil {
+		return nil, false, err
+	}
+
+	cm := &solver.CacheMap{
+		Digest: digest.FromBytes(dt),
+		Deps: make([]struct {
+			Selector          digest.Digest
+			ComputeDigestFunc solver.ResultBasedCacheFunc
+		}, f.numInputs),
+	}
+
+	for idx, m := range selectors {
+		if _, ok := invalidSelectors[idx]; ok {
+			continue
+		}
+		dgsts := make([][]byte, 0, len(m))
+		for k := range m {
+			dgsts = append(dgsts, []byte(k.Path))
+		}
+		sort.Slice(dgsts, func(i, j int) bool {
+			return bytes.Compare(dgsts[i], dgsts[j]) > 0
+		})
+		cm.Deps[idx].Selector = digest.FromBytes(bytes.Join(dgsts, []byte{0}))
+
+		cm.Deps[idx].ComputeDigestFunc = llbsolver.NewContentHashFunc(dedupeSelectors(m))
+	}
+
+	return cm, true, nil
+}
+
+func (f *fileOp) Exec(ctx context.Context, inputs []solver.Result) ([]solver.Result, error) {
+	inpRefs := make([]fileoptypes.Ref, 0, len(inputs))
+	for _, inp := range inputs {
+		workerRef, ok := inp.Sys().(*worker.WorkerRef)
+		if !ok {
+			return nil, errors.Errorf("invalid reference for exec %T", inp.Sys())
+		}
+		inpRefs = append(inpRefs, workerRef.ImmutableRef)
+	}
+
+	outs, err := f.solver.Solve(ctx, inpRefs, f.op.Actions)
+	if err != nil {
+		return nil, err
+	}
+
+	outResults := make([]solver.Result, 0, len(outs))
+	for _, out := range outs {
+		outResults = append(outResults, worker.NewWorkerRefResult(out.(cache.ImmutableRef), f.w))
+	}
+
+	return outResults, nil
+}
+
+func addSelector(m map[int]map[llbsolver.Selector]struct{}, idx int, sel string, wildcard, followLinks bool) {
+	mm, ok := m[idx]
+	if !ok {
+		mm = map[llbsolver.Selector]struct{}{}
+		m[idx] = mm
+	}
+	s := llbsolver.Selector{Path: sel}
+
+	if wildcard && containsWildcards(sel) {
+		s.Wildcard = true
+	}
+	if followLinks {
+		s.FollowLinks = true
+	}
+	mm[s] = struct{}{}
+}
+
+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
+}
+
+func dedupeSelectors(m map[llbsolver.Selector]struct{}) []llbsolver.Selector {
+	paths := make([]string, 0, len(m))
+	pathsFollow := make([]string, 0, len(m))
+	for sel := range m {
+		if !sel.Wildcard {
+			if sel.FollowLinks {
+				pathsFollow = append(pathsFollow, sel.Path)
+			} else {
+				paths = append(paths, sel.Path)
+			}
+		}
+	}
+	paths = dedupePaths(paths)
+	pathsFollow = dedupePaths(pathsFollow)
+	selectors := make([]llbsolver.Selector, 0, len(m))
+
+	for _, p := range paths {
+		selectors = append(selectors, llbsolver.Selector{Path: p})
+	}
+	for _, p := range pathsFollow {
+		selectors = append(selectors, llbsolver.Selector{Path: p, FollowLinks: true})
+	}
+
+	for sel := range m {
+		if sel.Wildcard {
+			selectors = append(selectors, sel)
+		}
+	}
+
+	sort.Slice(selectors, func(i, j int) bool {
+		return selectors[i].Path < selectors[j].Path
+	})
+
+	return selectors
+}
+
+func processOwner(chopt *pb.ChownOpt, selectors map[int]map[llbsolver.Selector]struct{}) error {
+	if chopt == nil {
+		return nil
+	}
+	if chopt.User != nil {
+		if u, ok := chopt.User.User.(*pb.UserOpt_ByName); ok {
+			if u.ByName.Input < 0 {
+				return errors.Errorf("invalid user index %d", u.ByName.Input)
+			}
+			addSelector(selectors, int(u.ByName.Input), "/etc/passwd", false, true)
+		}
+	}
+	if chopt.Group != nil {
+		if u, ok := chopt.Group.User.(*pb.UserOpt_ByName); ok {
+			if u.ByName.Input < 0 {
+				return errors.Errorf("invalid user index %d", u.ByName.Input)
+			}
+			addSelector(selectors, int(u.ByName.Input), "/etc/group", false, true)
+		}
+	}
+	return nil
+}
+
+func NewFileOpSolver(b fileoptypes.Backend, r fileoptypes.RefManager) *FileOpSolver {
+	return &FileOpSolver{
+		b:    b,
+		r:    r,
+		outs: map[int]int{},
+		ins:  map[int]input{},
+	}
+}
+
+type FileOpSolver struct {
+	b fileoptypes.Backend
+	r fileoptypes.RefManager
+
+	mu   sync.Mutex
+	outs map[int]int
+	ins  map[int]input
+	g    flightcontrol.Group
+}
+
+type input struct {
+	requiresCommit bool
+	mount          fileoptypes.Mount
+	ref            fileoptypes.Ref
+}
+
+func (s *FileOpSolver) Solve(ctx context.Context, inputs []fileoptypes.Ref, actions []*pb.FileAction) ([]fileoptypes.Ref, error) {
+	for i, a := range actions {
+		if int(a.Input) < -1 || int(a.Input) >= len(inputs)+len(actions) {
+			return nil, errors.Errorf("invalid input index %d, %d provided", a.Input, len(inputs)+len(actions))
+		}
+		if int(a.SecondaryInput) < -1 || int(a.SecondaryInput) >= len(inputs)+len(actions) {
+			return nil, errors.Errorf("invalid secondary input index %d, %d provided", a.Input, len(inputs))
+		}
+
+		inp, ok := s.ins[int(a.Input)]
+		if ok {
+			inp.requiresCommit = true
+		}
+		s.ins[int(a.Input)] = inp
+
+		inp, ok = s.ins[int(a.SecondaryInput)]
+		if ok {
+			inp.requiresCommit = true
+		}
+		s.ins[int(a.SecondaryInput)] = inp
+
+		if a.Output != -1 {
+			if _, ok := s.outs[int(a.Output)]; ok {
+				return nil, errors.Errorf("duplicate output %d", a.Output)
+			}
+			idx := len(inputs) + i
+			s.outs[int(a.Output)] = idx
+			s.ins[idx] = input{requiresCommit: true}
+		}
+	}
+
+	if len(s.outs) == 0 {
+		return nil, errors.Errorf("no outputs specified")
+	}
+
+	for i := 0; i < len(s.outs); i++ {
+		if _, ok := s.outs[i]; !ok {
+			return nil, errors.Errorf("missing output index %d", i)
+		}
+	}
+
+	defer func() {
+		for _, in := range s.ins {
+			if in.ref == nil && in.mount != nil {
+				in.mount.Release(context.TODO())
+			}
+		}
+	}()
+
+	outs := make([]fileoptypes.Ref, len(s.outs))
+
+	eg, ctx := errgroup.WithContext(ctx)
+	for i, idx := range s.outs {
+		func(i, idx int) {
+			eg.Go(func() error {
+				if err := s.validate(idx, inputs, actions, nil); err != nil {
+					return err
+				}
+				inp, err := s.getInput(ctx, idx, inputs, actions)
+				if err != nil {
+					return err
+				}
+				outs[i] = inp.ref
+				return nil
+			})
+		}(i, idx)
+	}
+
+	if err := eg.Wait(); err != nil {
+		for _, r := range outs {
+			if r != nil {
+				r.Release(context.TODO())
+			}
+		}
+		return nil, err
+	}
+
+	return outs, nil
+}
+
+func (s *FileOpSolver) validate(idx int, inputs []fileoptypes.Ref, actions []*pb.FileAction, loaded []int) error {
+	for _, check := range loaded {
+		if idx == check {
+			return errors.Errorf("loop from index %d", idx)
+		}
+	}
+	if idx < len(inputs) {
+		return nil
+	}
+	loaded = append(loaded, idx)
+	action := actions[idx-len(inputs)]
+	for _, inp := range []int{int(action.Input), int(action.SecondaryInput)} {
+		if err := s.validate(inp, inputs, actions, loaded); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *FileOpSolver) getInput(ctx context.Context, idx int, inputs []fileoptypes.Ref, actions []*pb.FileAction) (input, error) {
+	inp, err := s.g.Do(ctx, fmt.Sprintf("inp-%d", idx), func(ctx context.Context) (_ interface{}, err error) {
+		s.mu.Lock()
+		inp := s.ins[idx]
+		s.mu.Unlock()
+		if inp.mount != nil || inp.ref != nil {
+			return inp, nil
+		}
+
+		if idx < len(inputs) {
+			inp.ref = inputs[idx]
+			s.mu.Lock()
+			s.ins[idx] = inp
+			s.mu.Unlock()
+			return inp, nil
+		}
+
+		var inpMount, inpMountSecondary fileoptypes.Mount
+		var toRelease []fileoptypes.Mount
+		var inpMountPrepared bool
+		defer func() {
+			for _, m := range toRelease {
+				m.Release(context.TODO())
+			}
+			if err != nil && inpMount != nil && inpMountPrepared {
+				inpMount.Release(context.TODO())
+			}
+		}()
+
+		action := actions[idx-len(inputs)]
+
+		loadInput := func(ctx context.Context) func() error {
+			return func() error {
+				inp, err := s.getInput(ctx, int(action.Input), inputs, actions)
+				if err != nil {
+					return err
+				}
+				if inp.ref != nil {
+					m, err := s.r.Prepare(ctx, inp.ref, false)
+					if err != nil {
+						return err
+					}
+					inpMount = m
+					inpMountPrepared = true
+					return nil
+				}
+				inpMount = inp.mount
+				return nil
+			}
+		}
+
+		loadSecondaryInput := func(ctx context.Context) func() error {
+			return func() error {
+				inp, err := s.getInput(ctx, int(action.SecondaryInput), inputs, actions)
+				if err != nil {
+					return err
+				}
+				if inp.ref != nil {
+					m, err := s.r.Prepare(ctx, inp.ref, true)
+					if err != nil {
+						return err
+					}
+					inpMountSecondary = m
+					toRelease = append(toRelease, m)
+					return nil
+				}
+				inpMountSecondary = inp.mount
+				return nil
+			}
+		}
+
+		loadUser := func(ctx context.Context, uopt *pb.UserOpt) (fileoptypes.Mount, error) {
+			if uopt == nil {
+				return nil, nil
+			}
+			switch u := uopt.User.(type) {
+			case *pb.UserOpt_ByName:
+				var m fileoptypes.Mount
+				if u.ByName.Input < 0 {
+					return nil, errors.Errorf("invalid user index: %d", u.ByName.Input)
+				}
+				inp, err := s.getInput(ctx, int(u.ByName.Input), inputs, actions)
+				if err != nil {
+					return nil, err
+				}
+				if inp.ref != nil {
+					mm, err := s.r.Prepare(ctx, inp.ref, true)
+					if err != nil {
+						return nil, err
+					}
+					toRelease = append(toRelease, mm)
+					m = mm
+				} else {
+					m = inp.mount
+				}
+				return m, nil
+			default:
+				return nil, nil
+			}
+		}
+
+		loadOwner := func(ctx context.Context, chopt *pb.ChownOpt) (fileoptypes.Mount, fileoptypes.Mount, error) {
+			if chopt == nil {
+				return nil, nil, nil
+			}
+			um, err := loadUser(ctx, chopt.User)
+			if err != nil {
+				return nil, nil, err
+			}
+			gm, err := loadUser(ctx, chopt.Group)
+			if err != nil {
+				return nil, nil, err
+			}
+			return um, gm, nil
+		}
+
+		if action.Input != -1 && action.SecondaryInput != -1 {
+			eg, ctx := errgroup.WithContext(ctx)
+			eg.Go(loadInput(ctx))
+			eg.Go(loadSecondaryInput(ctx))
+			if err := eg.Wait(); err != nil {
+				return nil, err
+			}
+		} else {
+			if action.Input != -1 {
+				if err := loadInput(ctx)(); err != nil {
+					return nil, err
+				}
+			}
+			if action.SecondaryInput != -1 {
+				if err := loadSecondaryInput(ctx)(); err != nil {
+					return nil, err
+				}
+			}
+		}
+
+		if inpMount == nil {
+			m, err := s.r.Prepare(ctx, nil, false)
+			if err != nil {
+				return nil, err
+			}
+			inpMount = m
+			inpMountPrepared = true
+		}
+
+		switch a := action.Action.(type) {
+		case *pb.FileAction_Mkdir:
+			user, group, err := loadOwner(ctx, a.Mkdir.Owner)
+			if err != nil {
+				return nil, err
+			}
+			if err := s.b.Mkdir(ctx, inpMount, user, group, *a.Mkdir); err != nil {
+				return nil, err
+			}
+		case *pb.FileAction_Mkfile:
+			user, group, err := loadOwner(ctx, a.Mkfile.Owner)
+			if err != nil {
+				return nil, err
+			}
+			if err := s.b.Mkfile(ctx, inpMount, user, group, *a.Mkfile); err != nil {
+				return nil, err
+			}
+		case *pb.FileAction_Rm:
+			if err := s.b.Rm(ctx, inpMount, *a.Rm); err != nil {
+				return nil, err
+			}
+		case *pb.FileAction_Copy:
+			if inpMountSecondary == nil {
+				m, err := s.r.Prepare(ctx, nil, true)
+				if err != nil {
+					return nil, err
+				}
+				inpMountSecondary = m
+			}
+			user, group, err := loadOwner(ctx, a.Copy.Owner)
+			if err != nil {
+				return nil, err
+			}
+			if err := s.b.Copy(ctx, inpMountSecondary, inpMount, user, group, *a.Copy); err != nil {
+				return nil, err
+			}
+		default:
+			return nil, errors.Errorf("invalid action type %T", action.Action)
+		}
+
+		if inp.requiresCommit {
+			ref, err := s.r.Commit(ctx, inpMount)
+			if err != nil {
+				return nil, err
+			}
+			inp.ref = ref
+		} else {
+			inp.mount = inpMount
+		}
+		s.mu.Lock()
+		s.ins[idx] = inp
+		s.mu.Unlock()
+		return inp, nil
+	})
+	if err != nil {
+		return input{}, err
+	}
+	return inp.(input), err
+}

+ 28 - 0
vendor/github.com/moby/buildkit/solver/llbsolver/ops/fileoptypes/types.go

@@ -0,0 +1,28 @@
+package fileoptypes
+
+import (
+	"context"
+
+	"github.com/moby/buildkit/solver/pb"
+)
+
+type Ref interface {
+	Release(context.Context) error
+}
+
+type Mount interface {
+	IsFileOpMount()
+	Release(context.Context) error
+}
+
+type Backend interface {
+	Mkdir(context.Context, Mount, Mount, Mount, pb.FileActionMkDir) error
+	Mkfile(context.Context, Mount, Mount, Mount, pb.FileActionMkFile) error
+	Rm(context.Context, Mount, pb.FileActionRm) error
+	Copy(context.Context, Mount, Mount, Mount, Mount, pb.FileActionCopy) error
+}
+
+type RefManager interface {
+	Prepare(ctx context.Context, ref Ref, readonly bool) (Mount, error)
+	Commit(ctx context.Context, mount Mount) (Ref, error)
+}

+ 20 - 6
vendor/github.com/moby/buildkit/solver/llbsolver/result.go

@@ -13,7 +13,13 @@ import (
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/sync/errgroup"
 )
 )
 
 
-func NewContentHashFunc(selectors []string) solver.ResultBasedCacheFunc {
+type Selector struct {
+	Path        string
+	Wildcard    bool
+	FollowLinks bool
+}
+
+func NewContentHashFunc(selectors []Selector) solver.ResultBasedCacheFunc {
 	return func(ctx context.Context, res solver.Result) (digest.Digest, error) {
 	return func(ctx context.Context, res solver.Result) (digest.Digest, error) {
 		ref, ok := res.Sys().(*worker.WorkerRef)
 		ref, ok := res.Sys().(*worker.WorkerRef)
 		if !ok {
 		if !ok {
@@ -21,7 +27,7 @@ func NewContentHashFunc(selectors []string) solver.ResultBasedCacheFunc {
 		}
 		}
 
 
 		if len(selectors) == 0 {
 		if len(selectors) == 0 {
-			selectors = []string{""}
+			selectors = []Selector{{}}
 		}
 		}
 
 
 		dgsts := make([][]byte, len(selectors))
 		dgsts := make([][]byte, len(selectors))
@@ -32,11 +38,19 @@ func NewContentHashFunc(selectors []string) solver.ResultBasedCacheFunc {
 			// FIXME(tonistiigi): enabling this parallelization seems to create wrong results for some big inputs(like gobuild)
 			// FIXME(tonistiigi): enabling this parallelization seems to create wrong results for some big inputs(like gobuild)
 			// func(i int) {
 			// func(i int) {
 			// 	eg.Go(func() error {
 			// 	eg.Go(func() error {
-			dgst, err := contenthash.Checksum(ctx, ref.ImmutableRef, path.Join("/", sel), true)
-			if err != nil {
-				return "", err
+			if !sel.Wildcard {
+				dgst, err := contenthash.Checksum(ctx, ref.ImmutableRef, path.Join("/", sel.Path), sel.FollowLinks)
+				if err != nil {
+					return "", err
+				}
+				dgsts[i] = []byte(dgst)
+			} else {
+				dgst, err := contenthash.ChecksumWildcard(ctx, ref.ImmutableRef, path.Join("/", sel.Path), sel.FollowLinks)
+				if err != nil {
+					return "", err
+				}
+				dgsts[i] = []byte(dgst)
 			}
 			}
-			dgsts[i] = []byte(dgst)
 			// return nil
 			// return nil
 			// })
 			// })
 			// }(i)
 			// }(i)

+ 21 - 0
vendor/github.com/moby/buildkit/solver/llbsolver/vertex.go

@@ -1,6 +1,7 @@
 package llbsolver
 package llbsolver
 
 
 import (
 import (
+	"fmt"
 	"strings"
 	"strings"
 
 
 	"github.com/containerd/containerd/platforms"
 	"github.com/containerd/containerd/platforms"
@@ -228,9 +229,29 @@ func llbOpName(op *pb.Op) string {
 		return op.Source.Identifier
 		return op.Source.Identifier
 	case *pb.Op_Exec:
 	case *pb.Op_Exec:
 		return strings.Join(op.Exec.Meta.Args, " ")
 		return strings.Join(op.Exec.Meta.Args, " ")
+	case *pb.Op_File:
+		return fileOpName(op.File.Actions)
 	case *pb.Op_Build:
 	case *pb.Op_Build:
 		return "build"
 		return "build"
 	default:
 	default:
 		return "unknown"
 		return "unknown"
 	}
 	}
 }
 }
+
+func fileOpName(actions []*pb.FileAction) string {
+	names := make([]string, 0, len(actions))
+	for _, action := range actions {
+		switch a := action.Action.(type) {
+		case *pb.FileAction_Mkdir:
+			names = append(names, fmt.Sprintf("mkdir %s", a.Mkdir.Path))
+		case *pb.FileAction_Mkfile:
+			names = append(names, fmt.Sprintf("mkfile %s", a.Mkfile.Path))
+		case *pb.FileAction_Rm:
+			names = append(names, fmt.Sprintf("rm %s", a.Rm.Path))
+		case *pb.FileAction_Copy:
+			names = append(names, fmt.Sprintf("copy %s %s", a.Copy.Src, a.Copy.Dest))
+		}
+	}
+
+	return strings.Join(names, ", ")
+}

+ 2 - 0
vendor/github.com/moby/buildkit/solver/pb/attr.go

@@ -21,3 +21,5 @@ const AttrImageResolveModeDefault = "default"
 const AttrImageResolveModeForcePull = "pull"
 const AttrImageResolveModeForcePull = "pull"
 const AttrImageResolveModePreferLocal = "local"
 const AttrImageResolveModePreferLocal = "local"
 const AttrImageRecordType = "image.recordtype"
 const AttrImageRecordType = "image.recordtype"
+
+type IsFileAction = isFileAction_Action

+ 12 - 0
vendor/github.com/moby/buildkit/solver/pb/caps.go

@@ -43,6 +43,8 @@ const (
 	CapExecMountSSH            apicaps.CapID = "exec.mount.ssh"
 	CapExecMountSSH            apicaps.CapID = "exec.mount.ssh"
 	CapExecCgroupsMounted      apicaps.CapID = "exec.cgroup"
 	CapExecCgroupsMounted      apicaps.CapID = "exec.cgroup"
 
 
+	CapFileBase apicaps.CapID = "file.base"
+
 	CapConstraints apicaps.CapID = "constraints"
 	CapConstraints apicaps.CapID = "constraints"
 	CapPlatform    apicaps.CapID = "platform"
 	CapPlatform    apicaps.CapID = "platform"
 
 
@@ -226,6 +228,16 @@ func init() {
 		Status:  apicaps.CapStatusExperimental,
 		Status:  apicaps.CapStatusExperimental,
 	})
 	})
 
 
+	Caps.Init(apicaps.Cap{
+		ID:      CapFileBase,
+		Enabled: true,
+		Status:  apicaps.CapStatusPrerelease,
+		SupportedHint: map[string]string{
+			"docker":   "Docker v19.03",
+			"buildkit": "BuildKit v0.5.0",
+		},
+	})
+
 	Caps.Init(apicaps.Cap{
 	Caps.Init(apicaps.Cap{
 		ID:      CapConstraints,
 		ID:      CapConstraints,
 		Enabled: true,
 		Enabled: true,

File diff suppressed because it is too large
+ 806 - 607
vendor/github.com/moby/buildkit/solver/pb/ops.pb.go


+ 98 - 13
vendor/github.com/moby/buildkit/solver/pb/ops.proto

@@ -15,7 +15,7 @@ message Op {
 	oneof op {
 	oneof op {
 		ExecOp exec = 2;
 		ExecOp exec = 2;
 		SourceOp source = 3;
 		SourceOp source = 3;
-		CopyOp copy = 4;
+		FileOp file = 4;
 		BuildOp build = 5;
 		BuildOp build = 5;
 	}
 	}
 	Platform platform = 10;
 	Platform platform = 10;
@@ -134,18 +134,6 @@ message SSHOpt {
 	bool optional = 5;
 	bool optional = 5;
 }
 }
 
 
-// CopyOp copies files across Ops.
-message CopyOp {
-	repeated CopySource src = 1;
-	string dest = 2;
-}
-
-// CopySource specifies a source for CopyOp.
-message CopySource {
-	int64 input = 1 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false];
-	string selector = 2;
-}
-
 // SourceOp specifies a source such as build contexts and images.
 // SourceOp specifies a source such as build contexts and images.
 message SourceOp {
 message SourceOp {
 	// TODO: use source type or any type instead of URL protocol.
 	// TODO: use source type or any type instead of URL protocol.
@@ -211,4 +199,101 @@ message Definition {
 message HostIP {
 message HostIP {
 	string Host = 1;
 	string Host = 1;
 	string IP = 2;
 	string IP = 2;
+}
+
+message FileOp {
+	repeated FileAction actions = 2;
+}
+
+message FileAction {
+	int64 input = 1 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false]; // could be real input or target (target index + max input index)
+	int64 secondaryInput = 2 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false]; // --//--
+	int64 output = 3 [(gogoproto.customtype) = "OutputIndex", (gogoproto.nullable) = false];
+	oneof action {
+		// FileActionCopy copies files from secondaryInput on top of input
+		FileActionCopy copy = 4;
+		// FileActionMkFile creates a new file
+		FileActionMkFile mkfile = 5;
+		// FileActionMkDir creates a new directory
+		FileActionMkDir mkdir = 6;
+		// FileActionRm removes a file
+		FileActionRm rm = 7;
+	}
+}
+
+message FileActionCopy {
+	// src is the source path
+	string src = 1;
+	// dest path
+	string dest = 2;
+	// optional owner override
+	ChownOpt owner = 3;
+	// optional permission bits override
+	int32 mode = 4;
+	// followSymlink resolves symlinks in src
+	bool followSymlink = 5;
+	// dirCopyContents only copies contents if src is a directory
+	bool dirCopyContents = 6;
+	// attemptUnpackDockerCompatibility detects if src is an archive to unpack it instead
+	bool attemptUnpackDockerCompatibility = 7;
+	// createDestPath creates dest path directories if needed
+	bool createDestPath = 8;
+	// allowWildcard allows filepath.Match wildcards in src path
+	bool allowWildcard = 9;
+	// allowEmptyWildcard doesn't fail the whole copy if wildcard doesn't resolve to files
+	bool allowEmptyWildcard = 10;
+	// optional created time override
+	int64 timestamp = 11;
+}
+
+message FileActionMkFile {
+	// path for the new file
+	string path = 1;
+	// permission bits
+	int32 mode = 2;
+	// data is the new file contents
+	bytes data = 3;
+	// optional owner for the new file
+	ChownOpt owner = 4;
+	// optional created time override
+	int64 timestamp = 5;
+}
+
+message FileActionMkDir {
+	// path for the new directory
+	string path = 1;
+	// permission bits
+	int32 mode = 2;
+	// makeParents creates parent directories as well if needed
+	bool makeParents = 3;
+	// optional owner for the new directory
+	ChownOpt owner = 4;
+	// optional created time override
+	int64 timestamp = 5;
+}
+
+message FileActionRm {
+	// path to remove
+	string path = 1;
+	// allowNotFound doesn't fail the rm if file is not found
+	bool allowNotFound = 2;
+	// allowWildcard allows filepath.Match wildcards in path
+	bool allowWildcard = 3;
+}
+
+message ChownOpt {
+	UserOpt user = 1;
+	UserOpt group = 2;
+}
+
+message UserOpt {
+	oneof user {
+		NamedUserOpt byName = 1;
+		uint32 byID = 2;
+	}
+}
+
+message NamedUserOpt {
+	string name = 1;
+	int64 input = 2 [(gogoproto.customtype) = "InputIndex", (gogoproto.nullable) = false];
 }
 }

+ 1 - 1
vendor/github.com/moby/buildkit/util/flightcontrol/flightcontrol.go

@@ -24,7 +24,7 @@ type contextKeyT string
 
 
 var contextKey = contextKeyT("buildkit/util/flightcontrol.progress")
 var contextKey = contextKeyT("buildkit/util/flightcontrol.progress")
 
 
-// Group is a flightcontrol syncronization group
+// Group is a flightcontrol synchronization group
 type Group struct {
 type Group struct {
 	mu sync.Mutex       // protects m
 	mu sync.Mutex       // protects m
 	m  map[string]*call // lazily initialized
 	m  map[string]*call // lazily initialized

+ 11 - 1
vendor/github.com/moby/buildkit/util/imageutil/config.go

@@ -63,7 +63,7 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, cache Co
 	}
 	}
 
 
 	handlers := []images.Handler{
 	handlers := []images.Handler{
-		remotes.FetchHandler(cache, fetcher),
+		fetchWithoutRoot(remotes.FetchHandler(cache, fetcher)),
 		childrenConfigHandler(cache, platform),
 		childrenConfigHandler(cache, platform),
 	}
 	}
 	if err := images.Dispatch(ctx, images.Handlers(handlers...), nil, desc); err != nil {
 	if err := images.Dispatch(ctx, images.Handlers(handlers...), nil, desc); err != nil {
@@ -82,6 +82,16 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, cache Co
 	return desc.Digest, dt, nil
 	return desc.Digest, dt, nil
 }
 }
 
 
+func fetchWithoutRoot(fetch images.HandlerFunc) images.HandlerFunc {
+	return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
+		if desc.Annotations == nil {
+			desc.Annotations = map[string]string{}
+		}
+		desc.Annotations["buildkit/noroot"] = "true"
+		return fetch(ctx, desc)
+	}
+}
+
 func childrenConfigHandler(provider content.Provider, platform platforms.MatchComparer) images.HandlerFunc {
 func childrenConfigHandler(provider content.Provider, platform platforms.MatchComparer) images.HandlerFunc {
 	return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
 	return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) {
 		var descs []specs.Descriptor
 		var descs []specs.Descriptor

+ 423 - 0
vendor/github.com/tonistiigi/fsutil/copy/copy.go

@@ -0,0 +1,423 @@
+package fs
+
+import (
+	"context"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/containerd/continuity/fs"
+	"github.com/pkg/errors"
+)
+
+var bufferPool = &sync.Pool{
+	New: func() interface{} {
+		buffer := make([]byte, 32*1024)
+		return &buffer
+	},
+}
+
+func rootPath(root, p string, followLinks bool) (string, error) {
+	p = filepath.Join("/", p)
+	if p == "/" {
+		return root, nil
+	}
+	if followLinks {
+		return fs.RootPath(root, p)
+	}
+	d, f := filepath.Split(p)
+	ppath, err := fs.RootPath(root, d)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(ppath, f), nil
+}
+
+func ResolveWildcards(root, src string, followLinks bool) ([]string, error) {
+	d1, d2 := splitWildcards(src)
+	if d2 != "" {
+		p, err := rootPath(root, d1, followLinks)
+		if err != nil {
+			return nil, err
+		}
+		matches, err := resolveWildcards(p, d2)
+		if err != nil {
+			return nil, err
+		}
+		for i, m := range matches {
+			p, err := rel(root, m)
+			if err != nil {
+				return nil, err
+			}
+			matches[i] = p
+		}
+		return matches, nil
+	}
+	return []string{d1}, nil
+}
+
+// Copy copies files using `cp -a` semantics.
+// Copy is likely unsafe to be used in non-containerized environments.
+func Copy(ctx context.Context, srcRoot, src, dstRoot, dst string, opts ...Opt) error {
+	var ci CopyInfo
+	for _, o := range opts {
+		o(&ci)
+	}
+	ensureDstPath := dst
+	if d, f := filepath.Split(dst); f != "" && f != "." {
+		ensureDstPath = d
+	}
+	if ensureDstPath != "" {
+		ensureDstPath, err := fs.RootPath(dstRoot, ensureDstPath)
+		if err != nil {
+			return err
+		}
+		if err := MkdirAll(ensureDstPath, 0755, ci.Chown, ci.Utime); err != nil {
+			return err
+		}
+	}
+
+	dst, err := fs.RootPath(dstRoot, filepath.Clean(dst))
+	if err != nil {
+		return err
+	}
+
+	c := newCopier(ci.Chown, ci.Utime, ci.Mode, ci.XAttrErrorHandler)
+	srcs := []string{src}
+
+	if ci.AllowWildcards {
+		matches, err := ResolveWildcards(srcRoot, src, ci.FollowLinks)
+		if err != nil {
+			return err
+		}
+		if len(matches) == 0 {
+			return errors.Errorf("no matches found: %s", src)
+		}
+		srcs = matches
+	}
+
+	for _, src := range srcs {
+		srcFollowed, err := rootPath(srcRoot, src, ci.FollowLinks)
+		if err != nil {
+			return err
+		}
+		dst, err := c.prepareTargetDir(srcFollowed, src, dst, ci.CopyDirContents)
+		if err != nil {
+			return err
+		}
+		if err := c.copy(ctx, srcFollowed, dst, false); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (c *copier) prepareTargetDir(srcFollowed, src, destPath string, copyDirContents bool) (string, error) {
+	fiSrc, err := os.Lstat(srcFollowed)
+	if err != nil {
+		return "", err
+	}
+
+	fiDest, err := os.Stat(destPath)
+	if err != nil {
+		if !os.IsNotExist(err) {
+			return "", errors.Wrap(err, "failed to lstat destination path")
+		}
+	}
+
+	if (!copyDirContents && fiSrc.IsDir() && fiDest != nil) || (!fiSrc.IsDir() && fiDest != nil && fiDest.IsDir()) {
+		destPath = filepath.Join(destPath, filepath.Base(src))
+	}
+
+	target := filepath.Dir(destPath)
+
+	if copyDirContents && fiSrc.IsDir() && fiDest == nil {
+		target = destPath
+	}
+	if err := MkdirAll(target, 0755, c.chown, c.utime); err != nil {
+		return "", err
+	}
+
+	return destPath, nil
+}
+
+type ChownOpt struct {
+	Uid, Gid int
+}
+
+type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
+
+type CopyInfo struct {
+	Chown             *ChownOpt
+	Utime             *time.Time
+	AllowWildcards    bool
+	Mode              *int
+	XAttrErrorHandler XAttrErrorHandler
+	CopyDirContents   bool
+	FollowLinks       bool
+}
+
+type Opt func(*CopyInfo)
+
+func WithCopyInfo(ci CopyInfo) func(*CopyInfo) {
+	return func(c *CopyInfo) {
+		*c = ci
+	}
+}
+
+func WithChown(uid, gid int) Opt {
+	return func(ci *CopyInfo) {
+		ci.Chown = &ChownOpt{Uid: uid, Gid: gid}
+	}
+}
+
+func AllowWildcards(ci *CopyInfo) {
+	ci.AllowWildcards = true
+}
+
+func WithXAttrErrorHandler(h XAttrErrorHandler) Opt {
+	return func(ci *CopyInfo) {
+		ci.XAttrErrorHandler = h
+	}
+}
+
+func AllowXAttrErrors(ci *CopyInfo) {
+	h := func(string, string, string, error) error {
+		return nil
+	}
+	WithXAttrErrorHandler(h)(ci)
+}
+
+type copier struct {
+	chown             *ChownOpt
+	utime             *time.Time
+	mode              *int
+	inodes            map[uint64]string
+	xattrErrorHandler XAttrErrorHandler
+}
+
+func newCopier(chown *ChownOpt, tm *time.Time, mode *int, xeh XAttrErrorHandler) *copier {
+	if xeh == nil {
+		xeh = func(dst, src, key string, err error) error {
+			return err
+		}
+	}
+	return &copier{inodes: map[uint64]string{}, chown: chown, utime: tm, xattrErrorHandler: xeh, mode: mode}
+}
+
+// dest is always clean
+func (c *copier) copy(ctx context.Context, src, target string, overwriteTargetMetadata bool) error {
+	select {
+	case <-ctx.Done():
+		return ctx.Err()
+	default:
+	}
+	fi, err := os.Lstat(src)
+	if err != nil {
+		return errors.Wrapf(err, "failed to stat %s", src)
+	}
+
+	if !fi.IsDir() {
+		if err := ensureEmptyFileTarget(target); err != nil {
+			return err
+		}
+	}
+
+	copyFileInfo := true
+
+	switch {
+	case fi.IsDir():
+		if created, err := c.copyDirectory(ctx, src, target, fi, overwriteTargetMetadata); err != nil {
+			return err
+		} else if !overwriteTargetMetadata {
+			copyFileInfo = created
+		}
+	case (fi.Mode() & os.ModeType) == 0:
+		link, err := getLinkSource(target, fi, c.inodes)
+		if err != nil {
+			return errors.Wrap(err, "failed to get hardlink")
+		}
+		if link != "" {
+			if err := os.Link(link, target); err != nil {
+				return errors.Wrap(err, "failed to create hard link")
+			}
+		} else if err := copyFile(src, target); err != nil {
+			return errors.Wrap(err, "failed to copy files")
+		}
+	case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
+		link, err := os.Readlink(src)
+		if err != nil {
+			return errors.Wrapf(err, "failed to read link: %s", src)
+		}
+		if err := os.Symlink(link, target); err != nil {
+			return errors.Wrapf(err, "failed to create symlink: %s", target)
+		}
+	case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
+		if err := copyDevice(target, fi); err != nil {
+			return errors.Wrapf(err, "failed to create device")
+		}
+	default:
+		// TODO: Support pipes and sockets
+		return errors.Wrapf(err, "unsupported mode %s", fi.Mode())
+	}
+
+	if copyFileInfo {
+		if err := c.copyFileInfo(fi, target); err != nil {
+			return errors.Wrap(err, "failed to copy file info")
+		}
+
+		if err := copyXAttrs(target, src, c.xattrErrorHandler); err != nil {
+			return errors.Wrap(err, "failed to copy xattrs")
+		}
+	}
+	return nil
+}
+
+func (c *copier) copyDirectory(ctx context.Context, src, dst string, stat os.FileInfo, overwriteTargetMetadata bool) (bool, error) {
+	if !stat.IsDir() {
+		return false, errors.Errorf("source is not directory")
+	}
+
+	created := false
+
+	if st, err := os.Lstat(dst); err != nil {
+		if !os.IsNotExist(err) {
+			return false, err
+		}
+		created = true
+		if err := os.Mkdir(dst, stat.Mode()); err != nil {
+			return created, errors.Wrapf(err, "failed to mkdir %s", dst)
+		}
+	} else if !st.IsDir() {
+		return false, errors.Errorf("cannot copy to non-directory: %s", dst)
+	} else if overwriteTargetMetadata {
+		if err := os.Chmod(dst, stat.Mode()); err != nil {
+			return false, errors.Wrapf(err, "failed to chmod on %s", dst)
+		}
+	}
+
+	fis, err := ioutil.ReadDir(src)
+	if err != nil {
+		return false, errors.Wrapf(err, "failed to read %s", src)
+	}
+
+	for _, fi := range fis {
+		if err := c.copy(ctx, filepath.Join(src, fi.Name()), filepath.Join(dst, fi.Name()), true); err != nil {
+			return false, err
+		}
+	}
+
+	return created, nil
+}
+
+func ensureEmptyFileTarget(dst string) error {
+	fi, err := os.Lstat(dst)
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil
+		}
+		return errors.Wrap(err, "failed to lstat file target")
+	}
+	if fi.IsDir() {
+		return errors.Errorf("cannot replace to directory %s with file", dst)
+	}
+	return os.Remove(dst)
+}
+
+func copyFile(source, target string) error {
+	src, err := os.Open(source)
+	if err != nil {
+		return errors.Wrapf(err, "failed to open source %s", source)
+	}
+	defer src.Close()
+	tgt, err := os.Create(target)
+	if err != nil {
+		return errors.Wrapf(err, "failed to open target %s", target)
+	}
+	defer tgt.Close()
+
+	return copyFileContent(tgt, src)
+}
+
+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
+}
+
+func splitWildcards(p string) (d1, d2 string) {
+	parts := strings.Split(filepath.Join(p), string(filepath.Separator))
+	var p1, p2 []string
+	var found bool
+	for _, p := range parts {
+		if !found && containsWildcards(p) {
+			found = true
+		}
+		if p == "" {
+			p = "/"
+		}
+		if !found {
+			p1 = append(p1, p)
+		} else {
+			p2 = append(p2, p)
+		}
+	}
+	return filepath.Join(p1...), filepath.Join(p2...)
+}
+
+func resolveWildcards(basePath, comp string) ([]string, error) {
+	var out []string
+	err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		rel, err := rel(basePath, path)
+		if err != nil {
+			return err
+		}
+		if rel == "." {
+			return nil
+		}
+		if match, _ := filepath.Match(comp, rel); !match {
+			return nil
+		}
+		out = append(out, path)
+		if info.IsDir() {
+			return filepath.SkipDir
+		}
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// rel makes a path relative to base path. Same as `filepath.Rel` but can also
+// handle UUID paths in windows.
+func rel(basepath, targpath string) (string, error) {
+	// filepath.Rel can't handle UUID paths in windows
+	if runtime.GOOS == "windows" {
+		pfx := basepath + `\`
+		if strings.HasPrefix(targpath, pfx) {
+			p := strings.TrimPrefix(targpath, pfx)
+			if p == "" {
+				p = "."
+			}
+			return p, nil
+		}
+	}
+	return filepath.Rel(basepath, targpath)
+}

+ 97 - 0
vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go

@@ -0,0 +1,97 @@
+package fs
+
+import (
+	"io"
+	"math"
+	"os"
+	"syscall"
+
+	"github.com/containerd/containerd/sys"
+	"github.com/pkg/errors"
+	"golang.org/x/sys/unix"
+)
+
+func getUidGid(fi os.FileInfo) (uid, gid int) {
+	st := fi.Sys().(*syscall.Stat_t)
+	return int(st.Uid), int(st.Gid)
+}
+
+func (c *copier) copyFileInfo(fi os.FileInfo, name string) error {
+	st := fi.Sys().(*syscall.Stat_t)
+
+	chown := c.chown
+	if chown == nil {
+		uid, gid := getUidGid(fi)
+		chown = &ChownOpt{Uid: uid, Gid: gid}
+	}
+	if err := Chown(name, chown); err != nil {
+		return errors.Wrapf(err, "failed to chown %s", name)
+	}
+
+	m := fi.Mode()
+	if c.mode != nil {
+		m = (m & ^os.FileMode(0777)) | os.FileMode(*c.mode&0777)
+	}
+	if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
+		if err := os.Chmod(name, m); err != nil {
+			return errors.Wrapf(err, "failed to chmod %s", name)
+		}
+	}
+
+	if c.utime != nil {
+		if err := Utimes(name, c.utime); err != nil {
+			return err
+		}
+	} else {
+		timespec := []unix.Timespec{unix.Timespec(sys.StatAtime(st)), unix.Timespec(sys.StatMtime(st))}
+		if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
+			return errors.Wrapf(err, "failed to utime %s", name)
+		}
+	}
+
+	return nil
+}
+
+func copyFileContent(dst, src *os.File) error {
+	st, err := src.Stat()
+	if err != nil {
+		return errors.Wrap(err, "unable to stat source")
+	}
+
+	var written int64
+	size := st.Size()
+	first := true
+
+	for written < size {
+		var desired int
+		if size-written > math.MaxInt32 {
+			desired = int(math.MaxInt32)
+		} else {
+			desired = int(size - written)
+		}
+
+		n, err := unix.CopyFileRange(int(src.Fd()), nil, int(dst.Fd()), nil, desired, 0)
+		if err != nil {
+			if (err != unix.ENOSYS && err != unix.EXDEV && err != unix.EPERM) || !first {
+				return errors.Wrap(err, "copy file range failed")
+			}
+
+			buf := bufferPool.Get().(*[]byte)
+			_, err = io.CopyBuffer(dst, src, *buf)
+			bufferPool.Put(buf)
+			return errors.Wrap(err, "userspace copy failed")
+		}
+
+		first = false
+		written += int64(n)
+	}
+	return nil
+}
+
+func copyDevice(dst string, fi os.FileInfo) error {
+	st, ok := fi.Sys().(*syscall.Stat_t)
+	if !ok {
+		return errors.New("unsupported stat type")
+	}
+	return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
+}

+ 28 - 0
vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go

@@ -0,0 +1,28 @@
+// +build !windows
+
+package fs
+
+import (
+	"github.com/pkg/errors"
+
+	"github.com/containerd/continuity/sysx"
+)
+
+// copyXAttrs requires xeh to be non-nil
+func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
+	xattrKeys, err := sysx.LListxattr(src)
+	if err != nil {
+		return xeh(dst, src, "", errors.Wrapf(err, "failed to list xattrs on %s", src))
+	}
+	for _, xattr := range xattrKeys {
+		data, err := sysx.LGetxattr(src, xattr)
+		if err != nil {
+			return xeh(dst, src, xattr, errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src))
+		}
+		if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
+			return xeh(dst, src, xattr, errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst))
+		}
+	}
+
+	return nil
+}

+ 68 - 0
vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go

@@ -0,0 +1,68 @@
+// +build solaris darwin freebsd
+
+package fs
+
+import (
+	"io"
+	"os"
+	"syscall"
+
+	"github.com/containerd/containerd/sys"
+	"github.com/pkg/errors"
+	"golang.org/x/sys/unix"
+)
+
+func getUidGid(fi os.FileInfo) (uid, gid int) {
+	st := fi.Sys().(*syscall.Stat_t)
+	return int(st.Uid), int(st.Gid)
+}
+
+func (c *copier) copyFileInfo(fi os.FileInfo, name string) error {
+	st := fi.Sys().(*syscall.Stat_t)
+	chown := c.chown
+	if chown == nil {
+		uid, gid := getUidGid(fi)
+		chown = &ChownOpt{Uid: uid, Gid: gid}
+	}
+	if err := Chown(name, chown); err != nil {
+		return errors.Wrapf(err, "failed to chown %s", name)
+	}
+
+	m := fi.Mode()
+	if c.mode != nil {
+		m = (m & ^os.FileMode(0777)) | os.FileMode(*c.mode&0777)
+	}
+	if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
+		if err := os.Chmod(name, m); err != nil {
+			return errors.Wrapf(err, "failed to chmod %s", name)
+		}
+	}
+
+	if c.utime != nil {
+		if err := Utimes(name, c.utime); err != nil {
+			return err
+		}
+	} else {
+		timespec := []unix.Timespec{unix.Timespec(sys.StatAtime(st)), unix.Timespec(sys.StatMtime(st))}
+		if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
+			return errors.Wrapf(err, "failed to utime %s", name)
+		}
+	}
+	return nil
+}
+
+func copyFileContent(dst, src *os.File) error {
+	buf := bufferPool.Get().(*[]byte)
+	_, err := io.CopyBuffer(dst, src, *buf)
+	bufferPool.Put(buf)
+
+	return err
+}
+
+func copyDevice(dst string, fi os.FileInfo) error {
+	st, ok := fi.Sys().(*syscall.Stat_t)
+	if !ok {
+		return errors.New("unsupported stat type")
+	}
+	return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
+}

+ 33 - 0
vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go

@@ -0,0 +1,33 @@
+package fs
+
+import (
+	"io"
+	"os"
+
+	"github.com/pkg/errors"
+)
+
+func (c *copier) copyFileInfo(fi os.FileInfo, name string) error {
+	if err := os.Chmod(name, fi.Mode()); err != nil {
+		return errors.Wrapf(err, "failed to chmod %s", name)
+	}
+
+	// TODO: copy windows specific metadata
+
+	return nil
+}
+
+func copyFileContent(dst, src *os.File) error {
+	buf := bufferPool.Get().(*[]byte)
+	_, err := io.CopyBuffer(dst, src, *buf)
+	bufferPool.Put(buf)
+	return err
+}
+
+func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
+	return nil
+}
+
+func copyDevice(dst string, fi os.FileInfo) error {
+	return errors.New("device copy not supported")
+}

+ 27 - 0
vendor/github.com/tonistiigi/fsutil/copy/hardlink.go

@@ -0,0 +1,27 @@
+package fs
+
+import "os"
+
+// GetLinkInfo returns an identifier representing the node a hardlink is pointing
+// to. If the file is not hard linked then 0 will be returned.
+func GetLinkInfo(fi os.FileInfo) (uint64, bool) {
+	return getLinkInfo(fi)
+}
+
+// getLinkSource returns a path for the given name and
+// file info to its link source in the provided inode
+// map. If the given file name is not in the map and
+// has other links, it is added to the inode map
+// to be a source for other link locations.
+func getLinkSource(name string, fi os.FileInfo, inodes map[uint64]string) (string, error) {
+	inode, isHardlink := getLinkInfo(fi)
+	if !isHardlink {
+		return "", nil
+	}
+
+	path, ok := inodes[inode]
+	if !ok {
+		inodes[inode] = name
+	}
+	return path, nil
+}

+ 17 - 0
vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go

@@ -0,0 +1,17 @@
+// +build !windows
+
+package fs
+
+import (
+	"os"
+	"syscall"
+)
+
+func getLinkInfo(fi os.FileInfo) (uint64, bool) {
+	s, ok := fi.Sys().(*syscall.Stat_t)
+	if !ok {
+		return 0, false
+	}
+
+	return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1
+}

+ 7 - 0
vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go

@@ -0,0 +1,7 @@
+package fs
+
+import "os"
+
+func getLinkInfo(fi os.FileInfo) (uint64, bool) {
+	return 0, false
+}

+ 74 - 0
vendor/github.com/tonistiigi/fsutil/copy/mkdir.go

@@ -0,0 +1,74 @@
+package fs
+
+import (
+	"os"
+	"syscall"
+	"time"
+)
+
+func Chown(p string, user *ChownOpt) error {
+	if user != nil {
+		if err := os.Lchown(p, user.Uid, user.Gid); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// MkdirAll is forked os.MkdirAll
+func MkdirAll(path string, perm os.FileMode, user *ChownOpt, tm *time.Time) error {
+	// Fast path: if we can tell whether path is a directory or file, stop with success or error.
+	dir, err := os.Stat(path)
+	if err == nil {
+		if dir.IsDir() {
+			return nil
+		}
+		return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
+	}
+
+	// Slow path: make sure parent exists and then call Mkdir for path.
+	i := len(path)
+	for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
+		i--
+	}
+
+	j := i
+	for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
+		j--
+	}
+
+	if j > 1 {
+		// Create parent.
+		err = MkdirAll(fixRootDirectory(path[:j-1]), perm, user, tm)
+		if err != nil {
+			return err
+		}
+	}
+
+	dir, err1 := os.Lstat(path)
+	if err1 == nil && dir.IsDir() {
+		return nil
+	}
+
+	// Parent now exists; invoke Mkdir and use its result.
+	err = os.Mkdir(path, perm)
+	if err != nil {
+		// Handle arguments like "foo/." by
+		// double-checking that directory doesn't exist.
+		dir, err1 := os.Lstat(path)
+		if err1 == nil && dir.IsDir() {
+			return nil
+		}
+		return err
+	}
+
+	if err := Chown(path, user); err != nil {
+		return err
+	}
+
+	if err := Utimes(path, tm); err != nil {
+		return err
+	}
+
+	return nil
+}

+ 32 - 0
vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go

@@ -0,0 +1,32 @@
+// +build !windows
+
+package fs
+
+import (
+	"time"
+
+	"github.com/pkg/errors"
+	"golang.org/x/sys/unix"
+)
+
+func fixRootDirectory(p string) string {
+	return p
+}
+
+func Utimes(p string, tm *time.Time) error {
+	if tm == nil {
+		return nil
+	}
+
+	ts, err := unix.TimeToTimespec(*tm)
+	if err != nil {
+		return err
+	}
+
+	timespec := []unix.Timespec{ts, ts}
+	if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
+		return errors.Wrapf(err, "failed to utime %s", p)
+	}
+
+	return nil
+}

+ 21 - 0
vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go

@@ -0,0 +1,21 @@
+// +build windows
+
+package fs
+
+import (
+	"os"
+	"time"
+)
+
+func fixRootDirectory(p string) string {
+	if len(p) == len(`\\?\c:`) {
+		if os.IsPathSeparator(p[0]) && os.IsPathSeparator(p[1]) && p[2] == '?' && os.IsPathSeparator(p[3]) && p[5] == ':' {
+			return p + `\`
+		}
+	}
+	return p
+}
+
+func Utimes(p string, tm *time.Time) error {
+	return nil
+}

+ 8 - 2
vendor/github.com/tonistiigi/fsutil/diskwriter.go

@@ -26,7 +26,7 @@ type DiskWriterOpt struct {
 	Filter        FilterFunc
 	Filter        FilterFunc
 }
 }
 
 
-type FilterFunc func(*types.Stat) bool
+type FilterFunc func(string, *types.Stat) bool
 
 
 type DiskWriter struct {
 type DiskWriter struct {
 	opt  DiskWriterOpt
 	opt  DiskWriterOpt
@@ -84,6 +84,12 @@ func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, er
 	destPath := filepath.Join(dw.dest, filepath.FromSlash(p))
 	destPath := filepath.Join(dw.dest, filepath.FromSlash(p))
 
 
 	if kind == ChangeKindDelete {
 	if kind == ChangeKindDelete {
+		if dw.filter != nil {
+			var empty types.Stat
+			if ok := dw.filter(p, &empty); !ok {
+				return nil
+			}
+		}
 		// todo: no need to validate if diff is trusted but is it always?
 		// todo: no need to validate if diff is trusted but is it always?
 		if err := os.RemoveAll(destPath); err != nil {
 		if err := os.RemoveAll(destPath); err != nil {
 			return errors.Wrapf(err, "failed to remove: %s", destPath)
 			return errors.Wrapf(err, "failed to remove: %s", destPath)
@@ -104,7 +110,7 @@ func (dw *DiskWriter) HandleChange(kind ChangeKind, p string, fi os.FileInfo, er
 	statCopy := *stat
 	statCopy := *stat
 
 
 	if dw.filter != nil {
 	if dw.filter != nil {
-		if ok := dw.filter(&statCopy); !ok {
+		if ok := dw.filter(p, &statCopy); !ok {
 			return nil
 			return nil
 		}
 		}
 	}
 	}

+ 5 - 1
vendor/github.com/tonistiigi/fsutil/stat_unix.go

@@ -46,14 +46,18 @@ func setUnixOpt(fi os.FileInfo, stat *types.Stat, path string, seenFiles map[uin
 		}
 		}
 
 
 		ino := s.Ino
 		ino := s.Ino
+		linked := false
 		if seenFiles != nil {
 		if seenFiles != nil {
 			if s.Nlink > 1 {
 			if s.Nlink > 1 {
 				if oldpath, ok := seenFiles[ino]; ok {
 				if oldpath, ok := seenFiles[ino]; ok {
 					stat.Linkname = oldpath
 					stat.Linkname = oldpath
 					stat.Size_ = 0
 					stat.Size_ = 0
+					linked = true
 				}
 				}
 			}
 			}
-			seenFiles[ino] = path
+			if !linked {
+				seenFiles[ino] = path
+			}
 		}
 		}
 	}
 	}
 }
 }

+ 2 - 2
vendor/github.com/tonistiigi/fsutil/walker.go

@@ -19,7 +19,7 @@ type WalkOpt struct {
 	// FollowPaths contains symlinks that are resolved into include patterns
 	// FollowPaths contains symlinks that are resolved into include patterns
 	// before performing the fs walk
 	// before performing the fs walk
 	FollowPaths []string
 	FollowPaths []string
-	Map         func(*types.Stat) bool
+	Map         FilterFunc
 }
 }
 
 
 func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) error {
 func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) error {
@@ -157,7 +157,7 @@ func Walk(ctx context.Context, p string, opt *WalkOpt, fn filepath.WalkFunc) err
 			return ctx.Err()
 			return ctx.Err()
 		default:
 		default:
 			if opt != nil && opt.Map != nil {
 			if opt != nil && opt.Map != nil {
-				if allowed := opt.Map(stat); !allowed {
+				if allowed := opt.Map(stat.Path, stat); !allowed {
 					return nil
 					return nil
 				}
 				}
 			}
 			}

Some files were not shown because too many files changed in this diff