瀏覽代碼

Use continuity fs package for volume copy

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Brian Goff 7 年之前
父節點
當前提交
b3aab5e31f
共有 40 個文件被更改,包括 1412 次插入315 次删除
  1. 6 45
      container/container_unix.go
  2. 1 1
      vendor.conf
  3. 0 15
      vendor/github.com/containerd/continuity/devices/devices_darwin.go
  4. 0 23
      vendor/github.com/containerd/continuity/devices/devices_dummy.go
  5. 0 15
      vendor/github.com/containerd/continuity/devices/devices_freebsd.go
  6. 0 15
      vendor/github.com/containerd/continuity/devices/devices_linux.go
  7. 0 18
      vendor/github.com/containerd/continuity/devices/devices_solaris.go
  8. 13 10
      vendor/github.com/containerd/continuity/devices/devices_unix.go
  9. 119 0
      vendor/github.com/containerd/continuity/fs/copy.go
  10. 95 0
      vendor/github.com/containerd/continuity/fs/copy_linux.go
  11. 80 0
      vendor/github.com/containerd/continuity/fs/copy_unix.go
  12. 33 0
      vendor/github.com/containerd/continuity/fs/copy_windows.go
  13. 310 0
      vendor/github.com/containerd/continuity/fs/diff.go
  14. 58 0
      vendor/github.com/containerd/continuity/fs/diff_unix.go
  15. 32 0
      vendor/github.com/containerd/continuity/fs/diff_windows.go
  16. 87 0
      vendor/github.com/containerd/continuity/fs/dtype_linux.go
  17. 22 0
      vendor/github.com/containerd/continuity/fs/du.go
  18. 88 0
      vendor/github.com/containerd/continuity/fs/du_unix.go
  19. 60 0
      vendor/github.com/containerd/continuity/fs/du_windows.go
  20. 27 0
      vendor/github.com/containerd/continuity/fs/hardlink.go
  21. 18 0
      vendor/github.com/containerd/continuity/fs/hardlink_unix.go
  22. 7 0
      vendor/github.com/containerd/continuity/fs/hardlink_windows.go
  23. 276 0
      vendor/github.com/containerd/continuity/fs/path.go
  24. 28 0
      vendor/github.com/containerd/continuity/fs/stat_bsd.go
  25. 26 0
      vendor/github.com/containerd/continuity/fs/stat_linux.go
  26. 13 0
      vendor/github.com/containerd/continuity/fs/time.go
  27. 0 11
      vendor/github.com/containerd/continuity/sysx/copy_linux.go
  28. 0 20
      vendor/github.com/containerd/continuity/sysx/copy_linux_386.go
  29. 0 20
      vendor/github.com/containerd/continuity/sysx/copy_linux_amd64.go
  30. 0 20
      vendor/github.com/containerd/continuity/sysx/copy_linux_arm.go
  31. 0 20
      vendor/github.com/containerd/continuity/sysx/copy_linux_arm64.go
  32. 0 20
      vendor/github.com/containerd/continuity/sysx/copy_linux_ppc64le.go
  33. 0 20
      vendor/github.com/containerd/continuity/sysx/copy_linux_s390x.go
  34. 0 7
      vendor/github.com/containerd/continuity/sysx/sysnum_linux_386.go
  35. 0 7
      vendor/github.com/containerd/continuity/sysx/sysnum_linux_amd64.go
  36. 0 7
      vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm.go
  37. 0 7
      vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm64.go
  38. 0 7
      vendor/github.com/containerd/continuity/sysx/sysnum_linux_ppc64le.go
  39. 0 7
      vendor/github.com/containerd/continuity/sysx/sysnum_linux_s390x.go
  40. 13 0
      vendor/github.com/containerd/continuity/vendor.conf

+ 6 - 45
container/container_unix.go

@@ -6,13 +6,12 @@ import (
 	"io/ioutil"
 	"io/ioutil"
 	"os"
 	"os"
 
 
+	"github.com/containerd/continuity/fs"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	mounttypes "github.com/docker/docker/api/types/mount"
 	mounttypes "github.com/docker/docker/api/types/mount"
-	"github.com/docker/docker/pkg/chrootarchive"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/docker/pkg/stringid"
-	"github.com/docker/docker/pkg/system"
 	"github.com/docker/docker/volume"
 	"github.com/docker/docker/volume"
 	"github.com/opencontainers/selinux/go-selinux/label"
 	"github.com/opencontainers/selinux/go-selinux/label"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
@@ -398,53 +397,15 @@ func (container *Container) DetachAndUnmount(volumeEventLog func(name, action st
 // copyExistingContents copies from the source to the destination and
 // copyExistingContents copies from the source to the destination and
 // ensures the ownership is appropriately set.
 // ensures the ownership is appropriately set.
 func copyExistingContents(source, destination string) error {
 func copyExistingContents(source, destination string) error {
-	volList, err := ioutil.ReadDir(source)
+	dstList, err := ioutil.ReadDir(destination)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	if len(volList) > 0 {
-		srcList, err := ioutil.ReadDir(destination)
-		if err != nil {
-			return err
-		}
-		if len(srcList) == 0 {
-			// If the source volume is empty, copies files from the root into the volume
-			if err := chrootarchive.NewArchiver(nil).CopyWithTar(source, destination); err != nil {
-				return err
-			}
-		}
-	}
-	return copyOwnership(source, destination)
-}
-
-// copyOwnership copies the permissions and uid:gid of the source file
-// to the destination file
-func copyOwnership(source, destination string) error {
-	stat, err := system.Stat(source)
-	if err != nil {
-		return err
-	}
-
-	destStat, err := system.Stat(destination)
-	if err != nil {
-		return err
-	}
-
-	// In some cases, even though UID/GID match and it would effectively be a no-op,
-	// this can return a permission denied error... for example if this is an NFS
-	// mount.
-	// Since it's not really an error that we can't chown to the same UID/GID, don't
-	// even bother trying in such cases.
-	if stat.UID() != destStat.UID() || stat.GID() != destStat.GID() {
-		if err := os.Chown(destination, int(stat.UID()), int(stat.GID())); err != nil {
-			return err
-		}
-	}
-
-	if stat.Mode() != destStat.Mode() {
-		return os.Chmod(destination, os.FileMode(stat.Mode()))
+	if len(dstList) != 0 {
+		// destination is not empty, do not copy
+		return nil
 	}
 	}
-	return nil
+	return fs.CopyDir(destination, source)
 }
 }
 
 
 // TmpfsMounts returns the list of tmpfs mounts
 // TmpfsMounts returns the list of tmpfs mounts

+ 1 - 1
vendor.conf

@@ -106,7 +106,7 @@ google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
 # containerd
 # containerd
 github.com/containerd/containerd 3fa104f843ec92328912e042b767d26825f202aa
 github.com/containerd/containerd 3fa104f843ec92328912e042b767d26825f202aa
 github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6
 github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6
-github.com/containerd/continuity 35d55c5e8dd23b32037d56cf97174aff3efdfa83
+github.com/containerd/continuity 992a5f112bd2211d0983a1cc8562d2882848f3a3
 github.com/containerd/cgroups 29da22c6171a4316169f9205ab6c49f59b5b852f
 github.com/containerd/cgroups 29da22c6171a4316169f9205ab6c49f59b5b852f
 github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e
 github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e
 github.com/containerd/go-runc ed1cbe1fc31f5fb2359d3a54b6330d1a097858b7
 github.com/containerd/go-runc ed1cbe1fc31f5fb2359d3a54b6330d1a097858b7

+ 0 - 15
vendor/github.com/containerd/continuity/devices/devices_darwin.go

@@ -1,15 +0,0 @@
-package devices
-
-// from /usr/include/sys/types.h
-
-func getmajor(dev int32) uint64 {
-	return (uint64(dev) >> 24) & 0xff
-}
-
-func getminor(dev int32) uint64 {
-	return uint64(dev) & 0xffffff
-}
-
-func makedev(major int, minor int) int {
-	return ((major << 24) | minor)
-}

+ 0 - 23
vendor/github.com/containerd/continuity/devices/devices_dummy.go

@@ -1,23 +0,0 @@
-// +build solaris,!cgo
-
-//
-// Implementing the functions below requires cgo support.  Non-cgo stubs
-// versions are defined below to enable cross-compilation of source code
-// that depends on these functions, but the resultant cross-compiled
-// binaries cannot actually be used.  If the stub function(s) below are
-// actually invoked they will cause the calling process to exit.
-//
-
-package devices
-
-func getmajor(dev uint64) uint64 {
-	panic("getmajor() support requires cgo.")
-}
-
-func getminor(dev uint64) uint64 {
-	panic("getminor() support requires cgo.")
-}
-
-func makedev(major int, minor int) int {
-	panic("makedev() support requires cgo.")
-}

+ 0 - 15
vendor/github.com/containerd/continuity/devices/devices_freebsd.go

@@ -1,15 +0,0 @@
-package devices
-
-// from /usr/include/sys/types.h
-
-func getmajor(dev uint32) uint64 {
-	return (uint64(dev) >> 24) & 0xff
-}
-
-func getminor(dev uint32) uint64 {
-	return uint64(dev) & 0xffffff
-}
-
-func makedev(major int, minor int) int {
-	return ((major << 24) | minor)
-}

+ 0 - 15
vendor/github.com/containerd/continuity/devices/devices_linux.go

@@ -1,15 +0,0 @@
-package devices
-
-// from /usr/include/linux/kdev_t.h
-
-func getmajor(dev uint64) uint64 {
-	return dev >> 8
-}
-
-func getminor(dev uint64) uint64 {
-	return dev & 0xff
-}
-
-func makedev(major int, minor int) int {
-	return ((major << 8) | minor)
-}

+ 0 - 18
vendor/github.com/containerd/continuity/devices/devices_solaris.go

@@ -1,18 +0,0 @@
-// +build cgo
-
-package devices
-
-//#include <sys/mkdev.h>
-import "C"
-
-func getmajor(dev uint64) uint64 {
-	return uint64(C.major(C.dev_t(dev)))
-}
-
-func getminor(dev uint64) uint64 {
-	return uint64(C.minor(C.dev_t(dev)))
-}
-
-func makedev(major int, minor int) int {
-	return int(C.makedev(C.major_t(major), C.minor_t(minor)))
-}

+ 13 - 10
vendor/github.com/containerd/continuity/devices/devices_unix.go

@@ -6,6 +6,8 @@ import (
 	"fmt"
 	"fmt"
 	"os"
 	"os"
 	"syscall"
 	"syscall"
+
+	"golang.org/x/sys/unix"
 )
 )
 
 
 func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
 func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
@@ -14,42 +16,43 @@ func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
 		return 0, 0, fmt.Errorf("cannot extract device from os.FileInfo")
 		return 0, 0, fmt.Errorf("cannot extract device from os.FileInfo")
 	}
 	}
 
 
-	return getmajor(sys.Rdev), getminor(sys.Rdev), nil
+	dev := uint64(sys.Rdev)
+	return uint64(unix.Major(dev)), uint64(unix.Minor(dev)), nil
 }
 }
 
 
 // mknod provides a shortcut for syscall.Mknod
 // mknod provides a shortcut for syscall.Mknod
 func Mknod(p string, mode os.FileMode, maj, min int) error {
 func Mknod(p string, mode os.FileMode, maj, min int) error {
 	var (
 	var (
 		m   = syscallMode(mode.Perm())
 		m   = syscallMode(mode.Perm())
-		dev int
+		dev uint64
 	)
 	)
 
 
 	if mode&os.ModeDevice != 0 {
 	if mode&os.ModeDevice != 0 {
-		dev = makedev(maj, min)
+		dev = unix.Mkdev(uint32(maj), uint32(min))
 
 
 		if mode&os.ModeCharDevice != 0 {
 		if mode&os.ModeCharDevice != 0 {
-			m |= syscall.S_IFCHR
+			m |= unix.S_IFCHR
 		} else {
 		} else {
-			m |= syscall.S_IFBLK
+			m |= unix.S_IFBLK
 		}
 		}
 	} else if mode&os.ModeNamedPipe != 0 {
 	} else if mode&os.ModeNamedPipe != 0 {
-		m |= syscall.S_IFIFO
+		m |= unix.S_IFIFO
 	}
 	}
 
 
-	return syscall.Mknod(p, m, dev)
+	return unix.Mknod(p, m, int(dev))
 }
 }
 
 
 // syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
 // syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
 func syscallMode(i os.FileMode) (o uint32) {
 func syscallMode(i os.FileMode) (o uint32) {
 	o |= uint32(i.Perm())
 	o |= uint32(i.Perm())
 	if i&os.ModeSetuid != 0 {
 	if i&os.ModeSetuid != 0 {
-		o |= syscall.S_ISUID
+		o |= unix.S_ISUID
 	}
 	}
 	if i&os.ModeSetgid != 0 {
 	if i&os.ModeSetgid != 0 {
-		o |= syscall.S_ISGID
+		o |= unix.S_ISGID
 	}
 	}
 	if i&os.ModeSticky != 0 {
 	if i&os.ModeSticky != 0 {
-		o |= syscall.S_ISVTX
+		o |= unix.S_ISVTX
 	}
 	}
 	return
 	return
 }
 }

+ 119 - 0
vendor/github.com/containerd/continuity/fs/copy.go

@@ -0,0 +1,119 @@
+package fs
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sync"
+
+	"github.com/pkg/errors"
+)
+
+var bufferPool = &sync.Pool{
+	New: func() interface{} {
+		buffer := make([]byte, 32*1024)
+		return &buffer
+	},
+}
+
+// CopyDir copies the directory from src to dst.
+// Most efficient copy of files is attempted.
+func CopyDir(dst, src string) error {
+	inodes := map[uint64]string{}
+	return copyDirectory(dst, src, inodes)
+}
+
+func copyDirectory(dst, src string, inodes map[uint64]string) error {
+	stat, err := os.Stat(src)
+	if err != nil {
+		return errors.Wrapf(err, "failed to stat %s", src)
+	}
+	if !stat.IsDir() {
+		return errors.Errorf("source is not directory")
+	}
+
+	if st, err := os.Stat(dst); err != nil {
+		if err := os.Mkdir(dst, stat.Mode()); err != nil {
+			return errors.Wrapf(err, "failed to mkdir %s", dst)
+		}
+	} else if !st.IsDir() {
+		return errors.Errorf("cannot copy to non-directory: %s", dst)
+	} else {
+		if err := os.Chmod(dst, stat.Mode()); err != nil {
+			return errors.Wrapf(err, "failed to chmod on %s", dst)
+		}
+	}
+
+	fis, err := ioutil.ReadDir(src)
+	if err != nil {
+		return errors.Wrapf(err, "failed to read %s", src)
+	}
+
+	if err := copyFileInfo(stat, dst); err != nil {
+		return errors.Wrapf(err, "failed to copy file info for %s", dst)
+	}
+
+	for _, fi := range fis {
+		source := filepath.Join(src, fi.Name())
+		target := filepath.Join(dst, fi.Name())
+
+		switch {
+		case fi.IsDir():
+			if err := copyDirectory(target, source, inodes); err != nil {
+				return err
+			}
+			continue
+		case (fi.Mode() & os.ModeType) == 0:
+			link, err := getLinkSource(target, fi, 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(source, target); err != nil {
+				return errors.Wrap(err, "failed to copy files")
+			}
+		case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
+			link, err := os.Readlink(source)
+			if err != nil {
+				return errors.Wrapf(err, "failed to read link: %s", source)
+			}
+			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 err := copyFileInfo(fi, target); err != nil {
+			return errors.Wrap(err, "failed to copy file info")
+		}
+
+		if err := copyXAttrs(target, source); err != nil {
+			return errors.Wrap(err, "failed to copy xattrs")
+		}
+	}
+
+	return nil
+}
+
+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)
+}

+ 95 - 0
vendor/github.com/containerd/continuity/fs/copy_linux.go

@@ -0,0 +1,95 @@
+package fs
+
+import (
+	"io"
+	"os"
+	"syscall"
+
+	"github.com/containerd/continuity/sysx"
+	"github.com/pkg/errors"
+	"golang.org/x/sys/unix"
+)
+
+func copyFileInfo(fi os.FileInfo, name string) error {
+	st := fi.Sys().(*syscall.Stat_t)
+	if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
+		if os.IsPermission(err) {
+			// Normally if uid/gid are the same this would be a no-op, but some
+			// filesystems may still return EPERM... for instance NFS does this.
+			// In such a case, this is not an error.
+			if dstStat, err2 := os.Lstat(name); err2 == nil {
+				st2 := dstStat.Sys().(*syscall.Stat_t)
+				if st.Uid == st2.Uid && st.Gid == st2.Gid {
+					err = nil
+				}
+			}
+		}
+		if err != nil {
+			return errors.Wrapf(err, "failed to chown %s", name)
+		}
+	}
+
+	if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
+		if err := os.Chmod(name, fi.Mode()); err != nil {
+			return errors.Wrapf(err, "failed to chmod %s", name)
+		}
+	}
+
+	timespec := []unix.Timespec{unix.Timespec(StatAtime(st)), unix.Timespec(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")
+	}
+
+	n, err := unix.CopyFileRange(int(src.Fd()), nil, int(dst.Fd()), nil, int(st.Size()), 0)
+	if err != nil {
+		if err != unix.ENOSYS && err != unix.EXDEV {
+			return errors.Wrap(err, "copy file range failed")
+		}
+
+		buf := bufferPool.Get().(*[]byte)
+		_, err = io.CopyBuffer(dst, src, *buf)
+		bufferPool.Put(buf)
+		return err
+	}
+
+	if int64(n) != st.Size() {
+		return errors.Wrapf(err, "short copy: %d of %d", int64(n), st.Size())
+	}
+
+	return nil
+}
+
+func copyXAttrs(dst, src string) error {
+	xattrKeys, err := sysx.LListxattr(src)
+	if err != nil {
+		return errors.Wrapf(err, "failed to list xattrs on %s", src)
+	}
+	for _, xattr := range xattrKeys {
+		data, err := sysx.LGetxattr(src, xattr)
+		if err != nil {
+			return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
+		}
+		if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
+			return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
+		}
+	}
+
+	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))
+}

+ 80 - 0
vendor/github.com/containerd/continuity/fs/copy_unix.go

@@ -0,0 +1,80 @@
+// +build solaris darwin freebsd
+
+package fs
+
+import (
+	"io"
+	"os"
+	"syscall"
+
+	"github.com/containerd/continuity/sysx"
+	"github.com/pkg/errors"
+	"golang.org/x/sys/unix"
+)
+
+func copyFileInfo(fi os.FileInfo, name string) error {
+	st := fi.Sys().(*syscall.Stat_t)
+	if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
+		if os.IsPermission(err) {
+			// Normally if uid/gid are the same this would be a no-op, but some
+			// filesystems may still return EPERM... for instance NFS does this.
+			// In such a case, this is not an error.
+			if dstStat, err2 := os.Lstat(name); err2 == nil {
+				st2 := dstStat.Sys().(*syscall.Stat_t)
+				if st.Uid == st2.Uid && st.Gid == st2.Gid {
+					err = nil
+				}
+			}
+		}
+		if err != nil {
+			return errors.Wrapf(err, "failed to chown %s", name)
+		}
+	}
+
+	if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
+		if err := os.Chmod(name, fi.Mode()); err != nil {
+			return errors.Wrapf(err, "failed to chmod %s", name)
+		}
+	}
+
+	timespec := []syscall.Timespec{StatAtime(st), StatMtime(st)}
+	if err := syscall.UtimesNano(name, timespec); 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 copyXAttrs(dst, src string) error {
+	xattrKeys, err := sysx.LListxattr(src)
+	if err != nil {
+		return errors.Wrapf(err, "failed to list xattrs on %s", src)
+	}
+	for _, xattr := range xattrKeys {
+		data, err := sysx.LGetxattr(src, xattr)
+		if err != nil {
+			return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
+		}
+		if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
+			return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
+		}
+	}
+
+	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))
+}

+ 33 - 0
vendor/github.com/containerd/continuity/fs/copy_windows.go

@@ -0,0 +1,33 @@
+package fs
+
+import (
+	"io"
+	"os"
+
+	"github.com/pkg/errors"
+)
+
+func 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) error {
+	return nil
+}
+
+func copyDevice(dst string, fi os.FileInfo) error {
+	return errors.New("device copy not supported")
+}

+ 310 - 0
vendor/github.com/containerd/continuity/fs/diff.go

@@ -0,0 +1,310 @@
+package fs
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"golang.org/x/sync/errgroup"
+
+	"github.com/sirupsen/logrus"
+)
+
+// ChangeKind is the type of modification that
+// a change is making.
+type ChangeKind int
+
+const (
+	// ChangeKindUnmodified represents an unmodified
+	// file
+	ChangeKindUnmodified = iota
+
+	// ChangeKindAdd represents an addition of
+	// a file
+	ChangeKindAdd
+
+	// ChangeKindModify represents a change to
+	// an existing file
+	ChangeKindModify
+
+	// ChangeKindDelete represents a delete of
+	// a file
+	ChangeKindDelete
+)
+
+func (k ChangeKind) String() string {
+	switch k {
+	case ChangeKindUnmodified:
+		return "unmodified"
+	case ChangeKindAdd:
+		return "add"
+	case ChangeKindModify:
+		return "modify"
+	case ChangeKindDelete:
+		return "delete"
+	default:
+		return ""
+	}
+}
+
+// Change represents single change between a diff and its parent.
+type Change struct {
+	Kind ChangeKind
+	Path string
+}
+
+// ChangeFunc is the type of function called for each change
+// computed during a directory changes calculation.
+type ChangeFunc func(ChangeKind, string, os.FileInfo, error) error
+
+// Changes computes changes between two directories calling the
+// given change function for each computed change. The first
+// directory is intended to the base directory and second
+// directory the changed directory.
+//
+// The change callback is called by the order of path names and
+// should be appliable in that order.
+//  Due to this apply ordering, the following is true
+//  - Removed directory trees only create a single change for the root
+//    directory removed. Remaining changes are implied.
+//  - A directory which is modified to become a file will not have
+//    delete entries for sub-path items, their removal is implied
+//    by the removal of the parent directory.
+//
+// Opaque directories will not be treated specially and each file
+// removed from the base directory will show up as a removal.
+//
+// File content comparisons will be done on files which have timestamps
+// which may have been truncated. If either of the files being compared
+// has a zero value nanosecond value, each byte will be compared for
+// differences. If 2 files have the same seconds value but different
+// nanosecond values where one of those values is zero, the files will
+// be considered unchanged if the content is the same. This behavior
+// is to account for timestamp truncation during archiving.
+func Changes(ctx context.Context, a, b string, changeFn ChangeFunc) error {
+	if a == "" {
+		logrus.Debugf("Using single walk diff for %s", b)
+		return addDirChanges(ctx, changeFn, b)
+	} else if diffOptions := detectDirDiff(b, a); diffOptions != nil {
+		logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, a)
+		return diffDirChanges(ctx, changeFn, a, diffOptions)
+	}
+
+	logrus.Debugf("Using double walk diff for %s from %s", b, a)
+	return doubleWalkDiff(ctx, changeFn, a, b)
+}
+
+func addDirChanges(ctx context.Context, changeFn ChangeFunc, root string) error {
+	return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		// Rebase path
+		path, err = filepath.Rel(root, path)
+		if err != nil {
+			return err
+		}
+
+		path = filepath.Join(string(os.PathSeparator), path)
+
+		// Skip root
+		if path == string(os.PathSeparator) {
+			return nil
+		}
+
+		return changeFn(ChangeKindAdd, path, f, nil)
+	})
+}
+
+// diffDirOptions is used when the diff can be directly calculated from
+// a diff directory to its base, without walking both trees.
+type diffDirOptions struct {
+	diffDir      string
+	skipChange   func(string) (bool, error)
+	deleteChange func(string, string, os.FileInfo) (string, error)
+}
+
+// diffDirChanges walks the diff directory and compares changes against the base.
+func diffDirChanges(ctx context.Context, changeFn ChangeFunc, base string, o *diffDirOptions) error {
+	changedDirs := make(map[string]struct{})
+	return filepath.Walk(o.diffDir, func(path string, f os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		// Rebase path
+		path, err = filepath.Rel(o.diffDir, path)
+		if err != nil {
+			return err
+		}
+
+		path = filepath.Join(string(os.PathSeparator), path)
+
+		// Skip root
+		if path == string(os.PathSeparator) {
+			return nil
+		}
+
+		// TODO: handle opaqueness, start new double walker at this
+		// location to get deletes, and skip tree in single walker
+
+		if o.skipChange != nil {
+			if skip, err := o.skipChange(path); skip {
+				return err
+			}
+		}
+
+		var kind ChangeKind
+
+		deletedFile, err := o.deleteChange(o.diffDir, path, f)
+		if err != nil {
+			return err
+		}
+
+		// Find out what kind of modification happened
+		if deletedFile != "" {
+			path = deletedFile
+			kind = ChangeKindDelete
+			f = nil
+		} else {
+			// Otherwise, the file was added
+			kind = ChangeKindAdd
+
+			// ...Unless it already existed in a base, in which case, it's a modification
+			stat, err := os.Stat(filepath.Join(base, path))
+			if err != nil && !os.IsNotExist(err) {
+				return err
+			}
+			if err == nil {
+				// The file existed in the base, so that's a modification
+
+				// However, if it's a directory, maybe it wasn't actually modified.
+				// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
+				if stat.IsDir() && f.IsDir() {
+					if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
+						// Both directories are the same, don't record the change
+						return nil
+					}
+				}
+				kind = ChangeKindModify
+			}
+		}
+
+		// If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files.
+		// This block is here to ensure the change is recorded even if the
+		// modify time, mode and size of the parent directory in the rw and ro layers are all equal.
+		// Check https://github.com/docker/docker/pull/13590 for details.
+		if f.IsDir() {
+			changedDirs[path] = struct{}{}
+		}
+		if kind == ChangeKindAdd || kind == ChangeKindDelete {
+			parent := filepath.Dir(path)
+			if _, ok := changedDirs[parent]; !ok && parent != "/" {
+				pi, err := os.Stat(filepath.Join(o.diffDir, parent))
+				if err := changeFn(ChangeKindModify, parent, pi, err); err != nil {
+					return err
+				}
+				changedDirs[parent] = struct{}{}
+			}
+		}
+
+		return changeFn(kind, path, f, nil)
+	})
+}
+
+// doubleWalkDiff walks both directories to create a diff
+func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err error) {
+	g, ctx := errgroup.WithContext(ctx)
+
+	var (
+		c1 = make(chan *currentPath)
+		c2 = make(chan *currentPath)
+
+		f1, f2 *currentPath
+		rmdir  string
+	)
+	g.Go(func() error {
+		defer close(c1)
+		return pathWalk(ctx, a, c1)
+	})
+	g.Go(func() error {
+		defer close(c2)
+		return pathWalk(ctx, b, c2)
+	})
+	g.Go(func() error {
+		for c1 != nil || c2 != nil {
+			if f1 == nil && c1 != nil {
+				f1, err = nextPath(ctx, c1)
+				if err != nil {
+					return err
+				}
+				if f1 == nil {
+					c1 = nil
+				}
+			}
+
+			if f2 == nil && c2 != nil {
+				f2, err = nextPath(ctx, c2)
+				if err != nil {
+					return err
+				}
+				if f2 == nil {
+					c2 = nil
+				}
+			}
+			if f1 == nil && f2 == nil {
+				continue
+			}
+
+			var f os.FileInfo
+			k, p := pathChange(f1, f2)
+			switch k {
+			case ChangeKindAdd:
+				if rmdir != "" {
+					rmdir = ""
+				}
+				f = f2.f
+				f2 = nil
+			case ChangeKindDelete:
+				// Check if this file is already removed by being
+				// under of a removed directory
+				if rmdir != "" && strings.HasPrefix(f1.path, rmdir) {
+					f1 = nil
+					continue
+				} else if f1.f.IsDir() {
+					rmdir = f1.path + string(os.PathSeparator)
+				} else if rmdir != "" {
+					rmdir = ""
+				}
+				f1 = nil
+			case ChangeKindModify:
+				same, err := sameFile(f1, f2)
+				if err != nil {
+					return err
+				}
+				if f1.f.IsDir() && !f2.f.IsDir() {
+					rmdir = f1.path + string(os.PathSeparator)
+				} else if rmdir != "" {
+					rmdir = ""
+				}
+				f = f2.f
+				f1 = nil
+				f2 = nil
+				if same {
+					if !isLinked(f) {
+						continue
+					}
+					k = ChangeKindUnmodified
+				}
+			}
+			if err := changeFn(k, p, f, nil); err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+
+	return g.Wait()
+}

+ 58 - 0
vendor/github.com/containerd/continuity/fs/diff_unix.go

@@ -0,0 +1,58 @@
+// +build !windows
+
+package fs
+
+import (
+	"bytes"
+	"os"
+	"syscall"
+
+	"github.com/containerd/continuity/sysx"
+	"github.com/pkg/errors"
+)
+
+// detectDirDiff returns diff dir options if a directory could
+// be found in the mount info for upper which is the direct
+// diff with the provided lower directory
+func detectDirDiff(upper, lower string) *diffDirOptions {
+	// TODO: get mount options for upper
+	// TODO: detect AUFS
+	// TODO: detect overlay
+	return nil
+}
+
+// compareSysStat returns whether the stats are equivalent,
+// whether the files are considered the same file, and
+// an error
+func compareSysStat(s1, s2 interface{}) (bool, error) {
+	ls1, ok := s1.(*syscall.Stat_t)
+	if !ok {
+		return false, nil
+	}
+	ls2, ok := s2.(*syscall.Stat_t)
+	if !ok {
+		return false, nil
+	}
+
+	return ls1.Mode == ls2.Mode && ls1.Uid == ls2.Uid && ls1.Gid == ls2.Gid && ls1.Rdev == ls2.Rdev, nil
+}
+
+func compareCapabilities(p1, p2 string) (bool, error) {
+	c1, err := sysx.LGetxattr(p1, "security.capability")
+	if err != nil && err != sysx.ENODATA {
+		return false, errors.Wrapf(err, "failed to get xattr for %s", p1)
+	}
+	c2, err := sysx.LGetxattr(p2, "security.capability")
+	if err != nil && err != sysx.ENODATA {
+		return false, errors.Wrapf(err, "failed to get xattr for %s", p2)
+	}
+	return bytes.Equal(c1, c2), nil
+}
+
+func isLinked(f os.FileInfo) bool {
+	s, ok := f.Sys().(*syscall.Stat_t)
+	if !ok {
+		return false
+	}
+	return !f.IsDir() && s.Nlink > 1
+}

+ 32 - 0
vendor/github.com/containerd/continuity/fs/diff_windows.go

@@ -0,0 +1,32 @@
+package fs
+
+import (
+	"os"
+
+	"golang.org/x/sys/windows"
+)
+
+func detectDirDiff(upper, lower string) *diffDirOptions {
+	return nil
+}
+
+func compareSysStat(s1, s2 interface{}) (bool, error) {
+	f1, ok := s1.(windows.Win32FileAttributeData)
+	if !ok {
+		return false, nil
+	}
+	f2, ok := s2.(windows.Win32FileAttributeData)
+	if !ok {
+		return false, nil
+	}
+	return f1.FileAttributes == f2.FileAttributes, nil
+}
+
+func compareCapabilities(p1, p2 string) (bool, error) {
+	// TODO: Use windows equivalent
+	return true, nil
+}
+
+func isLinked(os.FileInfo) bool {
+	return false
+}

+ 87 - 0
vendor/github.com/containerd/continuity/fs/dtype_linux.go

@@ -0,0 +1,87 @@
+// +build linux
+
+package fs
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"syscall"
+	"unsafe"
+)
+
+func locateDummyIfEmpty(path string) (string, error) {
+	children, err := ioutil.ReadDir(path)
+	if err != nil {
+		return "", err
+	}
+	if len(children) != 0 {
+		return "", nil
+	}
+	dummyFile, err := ioutil.TempFile(path, "fsutils-dummy")
+	if err != nil {
+		return "", err
+	}
+	name := dummyFile.Name()
+	err = dummyFile.Close()
+	return name, err
+}
+
+// SupportsDType returns whether the filesystem mounted on path supports d_type
+func SupportsDType(path string) (bool, error) {
+	// locate dummy so that we have at least one dirent
+	dummy, err := locateDummyIfEmpty(path)
+	if err != nil {
+		return false, err
+	}
+	if dummy != "" {
+		defer os.Remove(dummy)
+	}
+
+	visited := 0
+	supportsDType := true
+	fn := func(ent *syscall.Dirent) bool {
+		visited++
+		if ent.Type == syscall.DT_UNKNOWN {
+			supportsDType = false
+			// stop iteration
+			return true
+		}
+		// continue iteration
+		return false
+	}
+	if err = iterateReadDir(path, fn); err != nil {
+		return false, err
+	}
+	if visited == 0 {
+		return false, fmt.Errorf("did not hit any dirent during iteration %s", path)
+	}
+	return supportsDType, nil
+}
+
+func iterateReadDir(path string, fn func(*syscall.Dirent) bool) error {
+	d, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	defer d.Close()
+	fd := int(d.Fd())
+	buf := make([]byte, 4096)
+	for {
+		nbytes, err := syscall.ReadDirent(fd, buf)
+		if err != nil {
+			return err
+		}
+		if nbytes == 0 {
+			break
+		}
+		for off := 0; off < nbytes; {
+			ent := (*syscall.Dirent)(unsafe.Pointer(&buf[off]))
+			if stop := fn(ent); stop {
+				return nil
+			}
+			off += int(ent.Reclen)
+		}
+	}
+	return nil
+}

+ 22 - 0
vendor/github.com/containerd/continuity/fs/du.go

@@ -0,0 +1,22 @@
+package fs
+
+import "context"
+
+// Usage of disk information
+type Usage struct {
+	Inodes int64
+	Size   int64
+}
+
+// DiskUsage counts the number of inodes and disk usage for the resources under
+// path.
+func DiskUsage(roots ...string) (Usage, error) {
+	return diskUsage(roots...)
+}
+
+// DiffUsage counts the numbers of inodes and disk usage in the
+// diff between the 2 directories. The first path is intended
+// as the base directory and the second as the changed directory.
+func DiffUsage(ctx context.Context, a, b string) (Usage, error) {
+	return diffUsage(ctx, a, b)
+}

+ 88 - 0
vendor/github.com/containerd/continuity/fs/du_unix.go

@@ -0,0 +1,88 @@
+// +build !windows
+
+package fs
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+	"syscall"
+)
+
+type inode struct {
+	// TODO(stevvooe): Can probably reduce memory usage by not tracking
+	// device, but we can leave this right for now.
+	dev, ino uint64
+}
+
+func newInode(stat *syscall.Stat_t) inode {
+	return inode{
+		// Dev is uint32 on darwin/bsd, uint64 on linux/solaris
+		dev: uint64(stat.Dev), // nolint: unconvert
+		// Ino is uint32 on bsd, uint64 on darwin/linux/solaris
+		ino: uint64(stat.Ino), // nolint: unconvert
+	}
+}
+
+func diskUsage(roots ...string) (Usage, error) {
+
+	var (
+		size   int64
+		inodes = map[inode]struct{}{} // expensive!
+	)
+
+	for _, root := range roots {
+		if err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+
+			inoKey := newInode(fi.Sys().(*syscall.Stat_t))
+			if _, ok := inodes[inoKey]; !ok {
+				inodes[inoKey] = struct{}{}
+				size += fi.Size()
+			}
+
+			return nil
+		}); err != nil {
+			return Usage{}, err
+		}
+	}
+
+	return Usage{
+		Inodes: int64(len(inodes)),
+		Size:   size,
+	}, nil
+}
+
+func diffUsage(ctx context.Context, a, b string) (Usage, error) {
+	var (
+		size   int64
+		inodes = map[inode]struct{}{} // expensive!
+	)
+
+	if err := Changes(ctx, a, b, func(kind ChangeKind, _ string, fi os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if kind == ChangeKindAdd || kind == ChangeKindModify {
+			inoKey := newInode(fi.Sys().(*syscall.Stat_t))
+			if _, ok := inodes[inoKey]; !ok {
+				inodes[inoKey] = struct{}{}
+				size += fi.Size()
+			}
+
+			return nil
+
+		}
+		return nil
+	}); err != nil {
+		return Usage{}, err
+	}
+
+	return Usage{
+		Inodes: int64(len(inodes)),
+		Size:   size,
+	}, nil
+}

+ 60 - 0
vendor/github.com/containerd/continuity/fs/du_windows.go

@@ -0,0 +1,60 @@
+// +build windows
+
+package fs
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+)
+
+func diskUsage(roots ...string) (Usage, error) {
+	var (
+		size int64
+	)
+
+	// TODO(stevvooe): Support inodes (or equivalent) for windows.
+
+	for _, root := range roots {
+		if err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+
+			size += fi.Size()
+			return nil
+		}); err != nil {
+			return Usage{}, err
+		}
+	}
+
+	return Usage{
+		Size: size,
+	}, nil
+}
+
+func diffUsage(ctx context.Context, a, b string) (Usage, error) {
+	var (
+		size int64
+	)
+
+	if err := Changes(ctx, a, b, func(kind ChangeKind, _ string, fi os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if kind == ChangeKindAdd || kind == ChangeKindModify {
+			size += fi.Size()
+
+			return nil
+
+		}
+		return nil
+	}); err != nil {
+		return Usage{}, err
+	}
+
+	return Usage{
+		Size: size,
+	}, nil
+}

+ 27 - 0
vendor/github.com/containerd/continuity/fs/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
+}

+ 18 - 0
vendor/github.com/containerd/continuity/fs/hardlink_unix.go

@@ -0,0 +1,18 @@
+// +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
+	}
+
+	// Ino is uint32 on bsd, uint64 on darwin/linux/solaris
+	return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1 // nolint: unconvert
+}

+ 7 - 0
vendor/github.com/containerd/continuity/fs/hardlink_windows.go

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

+ 276 - 0
vendor/github.com/containerd/continuity/fs/path.go

@@ -0,0 +1,276 @@
+package fs
+
+import (
+	"bytes"
+	"context"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/pkg/errors"
+)
+
+var (
+	errTooManyLinks = errors.New("too many links")
+)
+
+type currentPath struct {
+	path     string
+	f        os.FileInfo
+	fullPath string
+}
+
+func pathChange(lower, upper *currentPath) (ChangeKind, string) {
+	if lower == nil {
+		if upper == nil {
+			panic("cannot compare nil paths")
+		}
+		return ChangeKindAdd, upper.path
+	}
+	if upper == nil {
+		return ChangeKindDelete, lower.path
+	}
+	// TODO: compare by directory
+
+	switch i := strings.Compare(lower.path, upper.path); {
+	case i < 0:
+		// File in lower that is not in upper
+		return ChangeKindDelete, lower.path
+	case i > 0:
+		// File in upper that is not in lower
+		return ChangeKindAdd, upper.path
+	default:
+		return ChangeKindModify, upper.path
+	}
+}
+
+func sameFile(f1, f2 *currentPath) (bool, error) {
+	if os.SameFile(f1.f, f2.f) {
+		return true, nil
+	}
+
+	equalStat, err := compareSysStat(f1.f.Sys(), f2.f.Sys())
+	if err != nil || !equalStat {
+		return equalStat, err
+	}
+
+	if eq, err := compareCapabilities(f1.fullPath, f2.fullPath); err != nil || !eq {
+		return eq, err
+	}
+
+	// If not a directory also check size, modtime, and content
+	if !f1.f.IsDir() {
+		if f1.f.Size() != f2.f.Size() {
+			return false, nil
+		}
+		t1 := f1.f.ModTime()
+		t2 := f2.f.ModTime()
+
+		if t1.Unix() != t2.Unix() {
+			return false, nil
+		}
+
+		// If the timestamp may have been truncated in both of the
+		// files, check content of file to determine difference
+		if t1.Nanosecond() == 0 && t2.Nanosecond() == 0 {
+			var eq bool
+			if (f1.f.Mode() & os.ModeSymlink) == os.ModeSymlink {
+				eq, err = compareSymlinkTarget(f1.fullPath, f2.fullPath)
+			} else if f1.f.Size() > 0 {
+				eq, err = compareFileContent(f1.fullPath, f2.fullPath)
+			}
+			if err != nil || !eq {
+				return eq, err
+			}
+		} else if t1.Nanosecond() != t2.Nanosecond() {
+			return false, nil
+		}
+	}
+
+	return true, nil
+}
+
+func compareSymlinkTarget(p1, p2 string) (bool, error) {
+	t1, err := os.Readlink(p1)
+	if err != nil {
+		return false, err
+	}
+	t2, err := os.Readlink(p2)
+	if err != nil {
+		return false, err
+	}
+	return t1 == t2, nil
+}
+
+const compareChuckSize = 32 * 1024
+
+// compareFileContent compares the content of 2 same sized files
+// by comparing each byte.
+func compareFileContent(p1, p2 string) (bool, error) {
+	f1, err := os.Open(p1)
+	if err != nil {
+		return false, err
+	}
+	defer f1.Close()
+	f2, err := os.Open(p2)
+	if err != nil {
+		return false, err
+	}
+	defer f2.Close()
+
+	b1 := make([]byte, compareChuckSize)
+	b2 := make([]byte, compareChuckSize)
+	for {
+		n1, err1 := f1.Read(b1)
+		if err1 != nil && err1 != io.EOF {
+			return false, err1
+		}
+		n2, err2 := f2.Read(b2)
+		if err2 != nil && err2 != io.EOF {
+			return false, err2
+		}
+		if n1 != n2 || !bytes.Equal(b1[:n1], b2[:n2]) {
+			return false, nil
+		}
+		if err1 == io.EOF && err2 == io.EOF {
+			return true, nil
+		}
+	}
+}
+
+func pathWalk(ctx context.Context, root string, pathC chan<- *currentPath) error {
+	return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		// Rebase path
+		path, err = filepath.Rel(root, path)
+		if err != nil {
+			return err
+		}
+
+		path = filepath.Join(string(os.PathSeparator), path)
+
+		// Skip root
+		if path == string(os.PathSeparator) {
+			return nil
+		}
+
+		p := &currentPath{
+			path:     path,
+			f:        f,
+			fullPath: filepath.Join(root, path),
+		}
+
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case pathC <- p:
+			return nil
+		}
+	})
+}
+
+func nextPath(ctx context.Context, pathC <-chan *currentPath) (*currentPath, error) {
+	select {
+	case <-ctx.Done():
+		return nil, ctx.Err()
+	case p := <-pathC:
+		return p, nil
+	}
+}
+
+// RootPath joins a path with a root, evaluating and bounding any
+// symlink to the root directory.
+func RootPath(root, path string) (string, error) {
+	if path == "" {
+		return root, nil
+	}
+	var linksWalked int // to protect against cycles
+	for {
+		i := linksWalked
+		newpath, err := walkLinks(root, path, &linksWalked)
+		if err != nil {
+			return "", err
+		}
+		path = newpath
+		if i == linksWalked {
+			newpath = filepath.Join("/", newpath)
+			if path == newpath {
+				return filepath.Join(root, newpath), nil
+			}
+			path = newpath
+		}
+	}
+}
+
+func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
+	if *linksWalked > 255 {
+		return "", false, errTooManyLinks
+	}
+
+	path = filepath.Join("/", path)
+	if path == "/" {
+		return path, false, nil
+	}
+	realPath := filepath.Join(root, path)
+
+	fi, err := os.Lstat(realPath)
+	if err != nil {
+		// If path does not yet exist, treat as non-symlink
+		if os.IsNotExist(err) {
+			return path, false, nil
+		}
+		return "", false, err
+	}
+	if fi.Mode()&os.ModeSymlink == 0 {
+		return path, false, nil
+	}
+	newpath, err = os.Readlink(realPath)
+	if err != nil {
+		return "", false, err
+	}
+	if filepath.IsAbs(newpath) && strings.HasPrefix(newpath, root) {
+		newpath = newpath[:len(root)]
+		if !strings.HasPrefix(newpath, "/") {
+			newpath = "/" + newpath
+		}
+	}
+	*linksWalked++
+	return newpath, true, nil
+}
+
+func walkLinks(root, path string, linksWalked *int) (string, error) {
+	switch dir, file := filepath.Split(path); {
+	case dir == "":
+		newpath, _, err := walkLink(root, file, linksWalked)
+		return newpath, err
+	case file == "":
+		if os.IsPathSeparator(dir[len(dir)-1]) {
+			if dir == "/" {
+				return dir, nil
+			}
+			return walkLinks(root, dir[:len(dir)-1], linksWalked)
+		}
+		newpath, _, err := walkLink(root, dir, linksWalked)
+		return newpath, err
+	default:
+		newdir, err := walkLinks(root, dir, linksWalked)
+		if err != nil {
+			return "", err
+		}
+		newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
+		if err != nil {
+			return "", err
+		}
+		if !islink {
+			return newpath, nil
+		}
+		if filepath.IsAbs(newpath) {
+			return newpath, nil
+		}
+		return filepath.Join(newdir, newpath), nil
+	}
+}

+ 28 - 0
vendor/github.com/containerd/continuity/fs/stat_bsd.go

@@ -0,0 +1,28 @@
+// +build darwin freebsd
+
+package fs
+
+import (
+	"syscall"
+	"time"
+)
+
+// StatAtime returns the access time from a stat struct
+func StatAtime(st *syscall.Stat_t) syscall.Timespec {
+	return st.Atimespec
+}
+
+// StatCtime returns the created time from a stat struct
+func StatCtime(st *syscall.Stat_t) syscall.Timespec {
+	return st.Ctimespec
+}
+
+// StatMtime returns the modified time from a stat struct
+func StatMtime(st *syscall.Stat_t) syscall.Timespec {
+	return st.Mtimespec
+}
+
+// StatATimeAsTime returns the access time as a time.Time
+func StatATimeAsTime(st *syscall.Stat_t) time.Time {
+	return time.Unix(int64(st.Atimespec.Sec), int64(st.Atimespec.Nsec)) // nolint: unconvert
+}

+ 26 - 0
vendor/github.com/containerd/continuity/fs/stat_linux.go

@@ -0,0 +1,26 @@
+package fs
+
+import (
+	"syscall"
+	"time"
+)
+
+// StatAtime returns the Atim
+func StatAtime(st *syscall.Stat_t) syscall.Timespec {
+	return st.Atim
+}
+
+// StatCtime returns the Ctim
+func StatCtime(st *syscall.Stat_t) syscall.Timespec {
+	return st.Ctim
+}
+
+// StatMtime returns the Mtim
+func StatMtime(st *syscall.Stat_t) syscall.Timespec {
+	return st.Mtim
+}
+
+// StatATimeAsTime returns st.Atim as a time.Time
+func StatATimeAsTime(st *syscall.Stat_t) time.Time {
+	return time.Unix(st.Atim.Sec, st.Atim.Nsec)
+}

+ 13 - 0
vendor/github.com/containerd/continuity/fs/time.go

@@ -0,0 +1,13 @@
+package fs
+
+import "time"
+
+// Gnu tar and the go tar writer don't have sub-second mtime
+// precision, which is problematic when we apply changes via tar
+// files, we handle this by comparing for exact times, *or* same
+// second count and either a or b having exactly 0 nanoseconds
+func sameFsTime(a, b time.Time) bool {
+	return a == b ||
+		(a.Unix() == b.Unix() &&
+			(a.Nanosecond() == 0 || b.Nanosecond() == 0))
+}

+ 0 - 11
vendor/github.com/containerd/continuity/sysx/copy_linux.go

@@ -1,11 +0,0 @@
-package sysx
-
-// These functions will be generated by generate.sh
-//    $ GOOS=linux GOARCH=386 ./generate.sh copy
-//    $ GOOS=linux GOARCH=amd64 ./generate.sh copy
-//    $ GOOS=linux GOARCH=arm ./generate.sh copy
-//    $ GOOS=linux GOARCH=arm64 ./generate.sh copy
-//    $ GOOS=linux GOARCH=ppc64le ./generate.sh copy
-//    $ GOOS=linux GOARCH=s390x ./generate.sh copy
-
-//sys CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error)

+ 0 - 20
vendor/github.com/containerd/continuity/sysx/copy_linux_386.go

@@ -1,20 +0,0 @@
-// mksyscall.pl -l32 copy_linux.go
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
-
-package sysx
-
-import (
-	"syscall"
-	"unsafe"
-)
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
-func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
-	r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
-	n = int(r0)
-	if e1 != 0 {
-		err = errnoErr(e1)
-	}
-	return
-}

+ 0 - 20
vendor/github.com/containerd/continuity/sysx/copy_linux_amd64.go

@@ -1,20 +0,0 @@
-// mksyscall.pl copy_linux.go
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
-
-package sysx
-
-import (
-	"syscall"
-	"unsafe"
-)
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
-func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
-	r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
-	n = int(r0)
-	if e1 != 0 {
-		err = errnoErr(e1)
-	}
-	return
-}

+ 0 - 20
vendor/github.com/containerd/continuity/sysx/copy_linux_arm.go

@@ -1,20 +0,0 @@
-// mksyscall.pl -l32 copy_linux.go
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
-
-package sysx
-
-import (
-	"syscall"
-	"unsafe"
-)
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
-func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
-	r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
-	n = int(r0)
-	if e1 != 0 {
-		err = errnoErr(e1)
-	}
-	return
-}

+ 0 - 20
vendor/github.com/containerd/continuity/sysx/copy_linux_arm64.go

@@ -1,20 +0,0 @@
-// mksyscall.pl copy_linux.go
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
-
-package sysx
-
-import (
-	"syscall"
-	"unsafe"
-)
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
-func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
-	r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
-	n = int(r0)
-	if e1 != 0 {
-		err = errnoErr(e1)
-	}
-	return
-}

+ 0 - 20
vendor/github.com/containerd/continuity/sysx/copy_linux_ppc64le.go

@@ -1,20 +0,0 @@
-// mksyscall.pl copy_linux.go
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
-
-package sysx
-
-import (
-	"syscall"
-	"unsafe"
-)
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
-func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
-	r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
-	n = int(r0)
-	if e1 != 0 {
-		err = errnoErr(e1)
-	}
-	return
-}

+ 0 - 20
vendor/github.com/containerd/continuity/sysx/copy_linux_s390x.go

@@ -1,20 +0,0 @@
-// mksyscall.pl copy_linux.go
-// MACHINE GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
-
-package sysx
-
-import (
-	"syscall"
-	"unsafe"
-)
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
-func CopyFileRange(fdin uintptr, offin *int64, fdout uintptr, offout *int64, len int, flags int) (n int, err error) {
-	r0, _, e1 := syscall.Syscall6(SYS_COPY_FILE_RANGE, uintptr(fdin), uintptr(unsafe.Pointer(offin)), uintptr(fdout), uintptr(unsafe.Pointer(offout)), uintptr(len), uintptr(flags))
-	n = int(r0)
-	if e1 != 0 {
-		err = errnoErr(e1)
-	}
-	return
-}

+ 0 - 7
vendor/github.com/containerd/continuity/sysx/sysnum_linux_386.go

@@ -1,7 +0,0 @@
-package sysx
-
-const (
-	// SYS_COPYFILERANGE defined in Kernel 4.5+
-	// Number defined in /usr/include/asm/unistd_32.h
-	SYS_COPY_FILE_RANGE = 377
-)

+ 0 - 7
vendor/github.com/containerd/continuity/sysx/sysnum_linux_amd64.go

@@ -1,7 +0,0 @@
-package sysx
-
-const (
-	// SYS_COPYFILERANGE defined in Kernel 4.5+
-	// Number defined in /usr/include/asm/unistd_64.h
-	SYS_COPY_FILE_RANGE = 326
-)

+ 0 - 7
vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm.go

@@ -1,7 +0,0 @@
-package sysx
-
-const (
-	// SYS_COPY_FILE_RANGE defined in Kernel 4.5+
-	// Number defined in /usr/include/arm-linux-gnueabihf/asm/unistd.h
-	SYS_COPY_FILE_RANGE = 391
-)

+ 0 - 7
vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm64.go

@@ -1,7 +0,0 @@
-package sysx
-
-const (
-	// SYS_COPY_FILE_RANGE defined in Kernel 4.5+
-	// Number defined in /usr/include/asm-generic/unistd.h
-	SYS_COPY_FILE_RANGE = 285
-)

+ 0 - 7
vendor/github.com/containerd/continuity/sysx/sysnum_linux_ppc64le.go

@@ -1,7 +0,0 @@
-package sysx
-
-const (
-	// SYS_COPYFILERANGE defined in Kernel 4.5+
-	// Number defined in /usr/include/asm/unistd_64.h
-	SYS_COPY_FILE_RANGE = 379
-)

+ 0 - 7
vendor/github.com/containerd/continuity/sysx/sysnum_linux_s390x.go

@@ -1,7 +0,0 @@
-package sysx
-
-const (
-	// SYS_COPYFILERANGE defined in Kernel 4.5+
-	// Number defined in /usr/include/asm/unistd_64.h
-	SYS_COPY_FILE_RANGE = 375
-)

+ 13 - 0
vendor/github.com/containerd/continuity/vendor.conf

@@ -0,0 +1,13 @@
+bazil.org/fuse 371fbbdaa8987b715bdd21d6adc4c9b20155f748
+github.com/dustin/go-humanize bb3d318650d48840a39aa21a027c6630e198e626
+github.com/golang/protobuf 1e59b77b52bf8e4b449a57e6f79f21226d571845
+github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
+github.com/opencontainers/go-digest 279bed98673dd5bef374d3b6e4b09e2af76183bf
+github.com/pkg/errors f15c970de5b76fac0b59abb32d62c17cc7bed265
+github.com/sirupsen/logrus 89742aefa4b206dcf400792f3bd35b542998eb3b
+github.com/spf13/cobra 2da4a54c5ceefcee7ca5dd0eea1e18a3b6366489
+github.com/spf13/pflag 4c012f6dcd9546820e378d0bdda4d8fc772cdfea
+golang.org/x/crypto 9f005a07e0d31d45e6656d241bb5c0f2efd4bc94
+golang.org/x/net a337091b0525af65de94df2eb7e98bd9962dcbe2
+golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
+golang.org/x/sys 665f6529cca930e27b831a0d1dafffbe1c172924