From b3aab5e31faf04d8a29f17be55562e4d0c0cb364 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 12 Feb 2018 15:27:34 -0500 Subject: [PATCH] Use continuity fs package for volume copy Signed-off-by: Brian Goff --- container/container_unix.go | 51 +-- vendor.conf | 2 +- .../continuity/devices/devices_darwin.go | 15 - .../continuity/devices/devices_dummy.go | 23 -- .../continuity/devices/devices_freebsd.go | 15 - .../continuity/devices/devices_linux.go | 15 - .../continuity/devices/devices_solaris.go | 18 - .../continuity/devices/devices_unix.go | 23 +- .../containerd/continuity/fs/copy.go | 119 +++++++ .../containerd/continuity/fs/copy_linux.go | 95 ++++++ .../containerd/continuity/fs/copy_unix.go | 80 +++++ .../containerd/continuity/fs/copy_windows.go | 33 ++ .../containerd/continuity/fs/diff.go | 310 ++++++++++++++++++ .../containerd/continuity/fs/diff_unix.go | 58 ++++ .../containerd/continuity/fs/diff_windows.go | 32 ++ .../containerd/continuity/fs/dtype_linux.go | 87 +++++ .../github.com/containerd/continuity/fs/du.go | 22 ++ .../containerd/continuity/fs/du_unix.go | 88 +++++ .../containerd/continuity/fs/du_windows.go | 60 ++++ .../containerd/continuity/fs/hardlink.go | 27 ++ .../containerd/continuity/fs/hardlink_unix.go | 18 + .../continuity/fs/hardlink_windows.go | 7 + .../containerd/continuity/fs/path.go | 276 ++++++++++++++++ .../containerd/continuity/fs/stat_bsd.go | 28 ++ .../containerd/continuity/fs/stat_linux.go | 26 ++ .../containerd/continuity/fs/time.go | 13 + .../containerd/continuity/sysx/copy_linux.go | 11 - .../continuity/sysx/copy_linux_386.go | 20 -- .../continuity/sysx/copy_linux_amd64.go | 20 -- .../continuity/sysx/copy_linux_arm.go | 20 -- .../continuity/sysx/copy_linux_arm64.go | 20 -- .../continuity/sysx/copy_linux_ppc64le.go | 20 -- .../continuity/sysx/copy_linux_s390x.go | 20 -- .../continuity/sysx/sysnum_linux_386.go | 7 - .../continuity/sysx/sysnum_linux_amd64.go | 7 - .../continuity/sysx/sysnum_linux_arm.go | 7 - .../continuity/sysx/sysnum_linux_arm64.go | 7 - .../continuity/sysx/sysnum_linux_ppc64le.go | 7 - .../continuity/sysx/sysnum_linux_s390x.go | 7 - .../containerd/continuity/vendor.conf | 13 + 40 files changed, 1412 insertions(+), 315 deletions(-) delete mode 100644 vendor/github.com/containerd/continuity/devices/devices_darwin.go delete mode 100644 vendor/github.com/containerd/continuity/devices/devices_dummy.go delete mode 100644 vendor/github.com/containerd/continuity/devices/devices_freebsd.go delete mode 100644 vendor/github.com/containerd/continuity/devices/devices_linux.go delete mode 100644 vendor/github.com/containerd/continuity/devices/devices_solaris.go create mode 100644 vendor/github.com/containerd/continuity/fs/copy.go create mode 100644 vendor/github.com/containerd/continuity/fs/copy_linux.go create mode 100644 vendor/github.com/containerd/continuity/fs/copy_unix.go create mode 100644 vendor/github.com/containerd/continuity/fs/copy_windows.go create mode 100644 vendor/github.com/containerd/continuity/fs/diff.go create mode 100644 vendor/github.com/containerd/continuity/fs/diff_unix.go create mode 100644 vendor/github.com/containerd/continuity/fs/diff_windows.go create mode 100644 vendor/github.com/containerd/continuity/fs/dtype_linux.go create mode 100644 vendor/github.com/containerd/continuity/fs/du.go create mode 100644 vendor/github.com/containerd/continuity/fs/du_unix.go create mode 100644 vendor/github.com/containerd/continuity/fs/du_windows.go create mode 100644 vendor/github.com/containerd/continuity/fs/hardlink.go create mode 100644 vendor/github.com/containerd/continuity/fs/hardlink_unix.go create mode 100644 vendor/github.com/containerd/continuity/fs/hardlink_windows.go create mode 100644 vendor/github.com/containerd/continuity/fs/path.go create mode 100644 vendor/github.com/containerd/continuity/fs/stat_bsd.go create mode 100644 vendor/github.com/containerd/continuity/fs/stat_linux.go create mode 100644 vendor/github.com/containerd/continuity/fs/time.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/copy_linux.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/copy_linux_386.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/copy_linux_amd64.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/copy_linux_arm.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/copy_linux_arm64.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/copy_linux_ppc64le.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/copy_linux_s390x.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/sysnum_linux_386.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/sysnum_linux_amd64.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm64.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/sysnum_linux_ppc64le.go delete mode 100644 vendor/github.com/containerd/continuity/sysx/sysnum_linux_s390x.go create mode 100644 vendor/github.com/containerd/continuity/vendor.conf diff --git a/container/container_unix.go b/container/container_unix.go index 32805e5271..6f4d91b919 100644 --- a/container/container_unix.go +++ b/container/container_unix.go @@ -6,13 +6,12 @@ import ( "io/ioutil" "os" + "github.com/containerd/continuity/fs" "github.com/docker/docker/api/types" containertypes "github.com/docker/docker/api/types/container" 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/stringid" - "github.com/docker/docker/pkg/system" "github.com/docker/docker/volume" "github.com/opencontainers/selinux/go-selinux/label" "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 // ensures the ownership is appropriately set. func copyExistingContents(source, destination string) error { - volList, err := ioutil.ReadDir(source) + dstList, err := ioutil.ReadDir(destination) if err != nil { 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 - } - } + if len(dstList) != 0 { + // destination is not empty, do not copy + return nil } - 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())) - } - return nil + return fs.CopyDir(destination, source) } // TmpfsMounts returns the list of tmpfs mounts diff --git a/vendor.conf b/vendor.conf index d2e1a21ed9..8f773f9bae 100644 --- a/vendor.conf +++ b/vendor.conf @@ -106,7 +106,7 @@ google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 # containerd github.com/containerd/containerd 3fa104f843ec92328912e042b767d26825f202aa github.com/containerd/fifo fbfb6a11ec671efbe94ad1c12c2e98773f19e1e6 -github.com/containerd/continuity 35d55c5e8dd23b32037d56cf97174aff3efdfa83 +github.com/containerd/continuity 992a5f112bd2211d0983a1cc8562d2882848f3a3 github.com/containerd/cgroups 29da22c6171a4316169f9205ab6c49f59b5b852f github.com/containerd/console 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e github.com/containerd/go-runc ed1cbe1fc31f5fb2359d3a54b6330d1a097858b7 diff --git a/vendor/github.com/containerd/continuity/devices/devices_darwin.go b/vendor/github.com/containerd/continuity/devices/devices_darwin.go deleted file mode 100644 index 5041e66611..0000000000 --- a/vendor/github.com/containerd/continuity/devices/devices_darwin.go +++ /dev/null @@ -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) -} diff --git a/vendor/github.com/containerd/continuity/devices/devices_dummy.go b/vendor/github.com/containerd/continuity/devices/devices_dummy.go deleted file mode 100644 index 9a48330a56..0000000000 --- a/vendor/github.com/containerd/continuity/devices/devices_dummy.go +++ /dev/null @@ -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.") -} diff --git a/vendor/github.com/containerd/continuity/devices/devices_freebsd.go b/vendor/github.com/containerd/continuity/devices/devices_freebsd.go deleted file mode 100644 index a5c7b93189..0000000000 --- a/vendor/github.com/containerd/continuity/devices/devices_freebsd.go +++ /dev/null @@ -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) -} diff --git a/vendor/github.com/containerd/continuity/devices/devices_linux.go b/vendor/github.com/containerd/continuity/devices/devices_linux.go deleted file mode 100644 index 454cf668f5..0000000000 --- a/vendor/github.com/containerd/continuity/devices/devices_linux.go +++ /dev/null @@ -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) -} diff --git a/vendor/github.com/containerd/continuity/devices/devices_solaris.go b/vendor/github.com/containerd/continuity/devices/devices_solaris.go deleted file mode 100644 index 8819ac82f5..0000000000 --- a/vendor/github.com/containerd/continuity/devices/devices_solaris.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build cgo - -package devices - -//#include -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))) -} diff --git a/vendor/github.com/containerd/continuity/devices/devices_unix.go b/vendor/github.com/containerd/continuity/devices/devices_unix.go index 85e9a68c49..97fe6b19d2 100644 --- a/vendor/github.com/containerd/continuity/devices/devices_unix.go +++ b/vendor/github.com/containerd/continuity/devices/devices_unix.go @@ -6,6 +6,8 @@ import ( "fmt" "os" "syscall" + + "golang.org/x/sys/unix" ) 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 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 func Mknod(p string, mode os.FileMode, maj, min int) error { var ( m = syscallMode(mode.Perm()) - dev int + dev uint64 ) if mode&os.ModeDevice != 0 { - dev = makedev(maj, min) + dev = unix.Mkdev(uint32(maj), uint32(min)) if mode&os.ModeCharDevice != 0 { - m |= syscall.S_IFCHR + m |= unix.S_IFCHR } else { - m |= syscall.S_IFBLK + m |= unix.S_IFBLK } } 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. func syscallMode(i os.FileMode) (o uint32) { o |= uint32(i.Perm()) if i&os.ModeSetuid != 0 { - o |= syscall.S_ISUID + o |= unix.S_ISUID } if i&os.ModeSetgid != 0 { - o |= syscall.S_ISGID + o |= unix.S_ISGID } if i&os.ModeSticky != 0 { - o |= syscall.S_ISVTX + o |= unix.S_ISVTX } return } diff --git a/vendor/github.com/containerd/continuity/fs/copy.go b/vendor/github.com/containerd/continuity/fs/copy.go new file mode 100644 index 0000000000..e8f452819b --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/containerd/continuity/fs/copy_linux.go b/vendor/github.com/containerd/continuity/fs/copy_linux.go new file mode 100644 index 0000000000..cfab6756b8 --- /dev/null +++ b/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)) +} diff --git a/vendor/github.com/containerd/continuity/fs/copy_unix.go b/vendor/github.com/containerd/continuity/fs/copy_unix.go new file mode 100644 index 0000000000..29cbb81ed5 --- /dev/null +++ b/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)) +} diff --git a/vendor/github.com/containerd/continuity/fs/copy_windows.go b/vendor/github.com/containerd/continuity/fs/copy_windows.go new file mode 100644 index 0000000000..6fb3de5710 --- /dev/null +++ b/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") +} diff --git a/vendor/github.com/containerd/continuity/fs/diff.go b/vendor/github.com/containerd/continuity/fs/diff.go new file mode 100644 index 0000000000..f2300e845d --- /dev/null +++ b/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() +} diff --git a/vendor/github.com/containerd/continuity/fs/diff_unix.go b/vendor/github.com/containerd/continuity/fs/diff_unix.go new file mode 100644 index 0000000000..3751814443 --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/containerd/continuity/fs/diff_windows.go b/vendor/github.com/containerd/continuity/fs/diff_windows.go new file mode 100644 index 0000000000..8eed36507e --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/containerd/continuity/fs/dtype_linux.go b/vendor/github.com/containerd/continuity/fs/dtype_linux.go new file mode 100644 index 0000000000..cc06573f1b --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/containerd/continuity/fs/du.go b/vendor/github.com/containerd/continuity/fs/du.go new file mode 100644 index 0000000000..26f5333154 --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/containerd/continuity/fs/du_unix.go b/vendor/github.com/containerd/continuity/fs/du_unix.go new file mode 100644 index 0000000000..fe3426d278 --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/containerd/continuity/fs/du_windows.go b/vendor/github.com/containerd/continuity/fs/du_windows.go new file mode 100644 index 0000000000..3f852fc15e --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/containerd/continuity/fs/hardlink.go b/vendor/github.com/containerd/continuity/fs/hardlink.go new file mode 100644 index 0000000000..38da93813c --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/containerd/continuity/fs/hardlink_unix.go b/vendor/github.com/containerd/continuity/fs/hardlink_unix.go new file mode 100644 index 0000000000..a6f99778de --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/containerd/continuity/fs/hardlink_windows.go b/vendor/github.com/containerd/continuity/fs/hardlink_windows.go new file mode 100644 index 0000000000..ad8845a7fb --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/containerd/continuity/fs/path.go b/vendor/github.com/containerd/continuity/fs/path.go new file mode 100644 index 0000000000..13fb826385 --- /dev/null +++ b/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 := ¤tPath{ + 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 + } +} diff --git a/vendor/github.com/containerd/continuity/fs/stat_bsd.go b/vendor/github.com/containerd/continuity/fs/stat_bsd.go new file mode 100644 index 0000000000..a1b776fdf5 --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/containerd/continuity/fs/stat_linux.go b/vendor/github.com/containerd/continuity/fs/stat_linux.go new file mode 100644 index 0000000000..25bc652581 --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/containerd/continuity/fs/time.go b/vendor/github.com/containerd/continuity/fs/time.go new file mode 100644 index 0000000000..c336f4d881 --- /dev/null +++ b/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)) +} diff --git a/vendor/github.com/containerd/continuity/sysx/copy_linux.go b/vendor/github.com/containerd/continuity/sysx/copy_linux.go deleted file mode 100644 index 4d8581284a..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/copy_linux.go +++ /dev/null @@ -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) diff --git a/vendor/github.com/containerd/continuity/sysx/copy_linux_386.go b/vendor/github.com/containerd/continuity/sysx/copy_linux_386.go deleted file mode 100644 index c1368c5723..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/copy_linux_386.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/containerd/continuity/sysx/copy_linux_amd64.go b/vendor/github.com/containerd/continuity/sysx/copy_linux_amd64.go deleted file mode 100644 index 9941b01f09..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/copy_linux_amd64.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/containerd/continuity/sysx/copy_linux_arm.go b/vendor/github.com/containerd/continuity/sysx/copy_linux_arm.go deleted file mode 100644 index c1368c5723..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/copy_linux_arm.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/containerd/continuity/sysx/copy_linux_arm64.go b/vendor/github.com/containerd/continuity/sysx/copy_linux_arm64.go deleted file mode 100644 index 9941b01f09..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/copy_linux_arm64.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/containerd/continuity/sysx/copy_linux_ppc64le.go b/vendor/github.com/containerd/continuity/sysx/copy_linux_ppc64le.go deleted file mode 100644 index 9941b01f09..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/copy_linux_ppc64le.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/containerd/continuity/sysx/copy_linux_s390x.go b/vendor/github.com/containerd/continuity/sysx/copy_linux_s390x.go deleted file mode 100644 index 9941b01f09..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/copy_linux_s390x.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_386.go b/vendor/github.com/containerd/continuity/sysx/sysnum_linux_386.go deleted file mode 100644 index 0063f8a913..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_386.go +++ /dev/null @@ -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 -) diff --git a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_amd64.go b/vendor/github.com/containerd/continuity/sysx/sysnum_linux_amd64.go deleted file mode 100644 index 4170540c5d..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_amd64.go +++ /dev/null @@ -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 -) diff --git a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm.go b/vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm.go deleted file mode 100644 index a05dcbb5ef..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm.go +++ /dev/null @@ -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 -) diff --git a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm64.go b/vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm64.go deleted file mode 100644 index da31bbd908..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_arm64.go +++ /dev/null @@ -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 -) diff --git a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_ppc64le.go b/vendor/github.com/containerd/continuity/sysx/sysnum_linux_ppc64le.go deleted file mode 100644 index 5dea25a3c4..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_ppc64le.go +++ /dev/null @@ -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 -) diff --git a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_s390x.go b/vendor/github.com/containerd/continuity/sysx/sysnum_linux_s390x.go deleted file mode 100644 index 8a6f2a7ec0..0000000000 --- a/vendor/github.com/containerd/continuity/sysx/sysnum_linux_s390x.go +++ /dev/null @@ -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 -) diff --git a/vendor/github.com/containerd/continuity/vendor.conf b/vendor/github.com/containerd/continuity/vendor.conf new file mode 100644 index 0000000000..7c80deec58 --- /dev/null +++ b/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