From 171feaafeb414c2206af08bf5df13d165a07c6dd Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Fri, 15 Mar 2019 16:31:09 -0700 Subject: [PATCH] vendor: add fsutil copy package Signed-off-by: Tonis Tiigi --- go.mod | 2 +- go.sum | 4 +- .../github.com/tonistiigi/fsutil/copy/copy.go | 423 ++++++++++++++++++ .../tonistiigi/fsutil/copy/copy_linux.go | 97 ++++ .../tonistiigi/fsutil/copy/copy_nowindows.go | 28 ++ .../tonistiigi/fsutil/copy/copy_unix.go | 68 +++ .../tonistiigi/fsutil/copy/copy_windows.go | 33 ++ .../tonistiigi/fsutil/copy/hardlink.go | 27 ++ .../tonistiigi/fsutil/copy/hardlink_unix.go | 17 + .../fsutil/copy/hardlink_windows.go | 7 + .../tonistiigi/fsutil/copy/mkdir.go | 74 +++ .../tonistiigi/fsutil/copy/mkdir_unix.go | 32 ++ .../tonistiigi/fsutil/copy/mkdir_windows.go | 21 + vendor/modules.txt | 3 +- 14 files changed, 832 insertions(+), 4 deletions(-) create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/copy.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/hardlink.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/mkdir.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go create mode 100644 vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go diff --git a/go.mod b/go.mod index a6236013..e67959dc 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( github.com/sirupsen/logrus v1.0.3 github.com/stretchr/testify v1.3.0 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 // indirect - github.com/tonistiigi/fsutil v0.0.0-20190314220245-1ec1983587cd + github.com/tonistiigi/fsutil v0.0.0-20190316003333-2a10686c7e92 github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea github.com/uber/jaeger-client-go v0.0.0-20180103221425-e02c85f9069e github.com/uber/jaeger-lib v1.2.1 // indirect diff --git a/go.sum b/go.sum index 29c54a1b..c6fce101 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 h1:zLV6q4e8Jv9EHjNg/iHfzwDkCve6Ua5jCygptrtXHvI= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tonistiigi/fsutil v0.0.0-20190314220245-1ec1983587cd h1:TT5pfgTCocfXTnVeEZRKSEsO8vAGM+OMZOSSXEO6ixw= -github.com/tonistiigi/fsutil v0.0.0-20190314220245-1ec1983587cd/go.mod h1:pzh7kdwkDRh+Bx8J30uqaKJ1M4QrSH/um8fcIXeM8rc= +github.com/tonistiigi/fsutil v0.0.0-20190316003333-2a10686c7e92 h1:+Njk7pGJkAqK0k007oRFmr9xSmZUA+VjV0SdW0ctqXs= +github.com/tonistiigi/fsutil v0.0.0-20190316003333-2a10686c7e92/go.mod h1:pzh7kdwkDRh+Bx8J30uqaKJ1M4QrSH/um8fcIXeM8rc= github.com/tonistiigi/go-immutable-radix v0.0.0-20170803185627-826af9ccf0fe h1:pd7hrFSqUPxYS9IB+UMG1AB/8EXGXo17ssx0bSQ5L6Y= github.com/tonistiigi/go-immutable-radix v0.0.0-20170803185627-826af9ccf0fe/go.mod h1:/+MCh11CJf2oz0BXmlmqyopK/ad1rKkcOXPoYuPCJYU= github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= diff --git a/vendor/github.com/tonistiigi/fsutil/copy/copy.go b/vendor/github.com/tonistiigi/fsutil/copy/copy.go new file mode 100644 index 00000000..a57f9dc8 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/copy.go @@ -0,0 +1,423 @@ +package fs + +import ( + "context" + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "sync" + "time" + + "github.com/containerd/continuity/fs" + "github.com/pkg/errors" +) + +var bufferPool = &sync.Pool{ + New: func() interface{} { + buffer := make([]byte, 32*1024) + return &buffer + }, +} + +func rootPath(root, p string, followLinks bool) (string, error) { + p = filepath.Join("/", p) + if p == "/" { + return root, nil + } + if followLinks { + return fs.RootPath(root, p) + } + d, f := filepath.Split(p) + ppath, err := fs.RootPath(root, d) + if err != nil { + return "", err + } + return filepath.Join(ppath, f), nil +} + +func ResolveWildcards(root, src string, followLinks bool) ([]string, error) { + d1, d2 := splitWildcards(src) + if d2 != "" { + p, err := rootPath(root, d1, followLinks) + if err != nil { + return nil, err + } + matches, err := resolveWildcards(p, d2) + if err != nil { + return nil, err + } + for i, m := range matches { + p, err := rel(root, m) + if err != nil { + return nil, err + } + matches[i] = p + } + return matches, nil + } + return []string{d1}, nil +} + +// Copy copies files using `cp -a` semantics. +// Copy is likely unsafe to be used in non-containerized environments. +func Copy(ctx context.Context, srcRoot, src, dstRoot, dst string, opts ...Opt) error { + var ci CopyInfo + for _, o := range opts { + o(&ci) + } + ensureDstPath := dst + if d, f := filepath.Split(dst); f != "" && f != "." { + ensureDstPath = d + } + if ensureDstPath != "" { + ensureDstPath, err := fs.RootPath(dstRoot, ensureDstPath) + if err != nil { + return err + } + if err := MkdirAll(ensureDstPath, 0755, ci.Chown, ci.Utime); err != nil { + return err + } + } + + dst, err := fs.RootPath(dstRoot, filepath.Clean(dst)) + if err != nil { + return err + } + + c := newCopier(ci.Chown, ci.Utime, ci.Mode, ci.XAttrErrorHandler) + srcs := []string{src} + + if ci.AllowWildcards { + matches, err := ResolveWildcards(srcRoot, src, ci.FollowLinks) + if err != nil { + return err + } + if len(matches) == 0 { + return errors.Errorf("no matches found: %s", src) + } + srcs = matches + } + + for _, src := range srcs { + srcFollowed, err := rootPath(srcRoot, src, ci.FollowLinks) + if err != nil { + return err + } + dst, err := c.prepareTargetDir(srcFollowed, src, dst, ci.CopyDirContents) + if err != nil { + return err + } + if err := c.copy(ctx, srcFollowed, dst, false); err != nil { + return err + } + } + + return nil +} + +func (c *copier) prepareTargetDir(srcFollowed, src, destPath string, copyDirContents bool) (string, error) { + fiSrc, err := os.Lstat(srcFollowed) + if err != nil { + return "", err + } + + fiDest, err := os.Stat(destPath) + if err != nil { + if !os.IsNotExist(err) { + return "", errors.Wrap(err, "failed to lstat destination path") + } + } + + if (!copyDirContents && fiSrc.IsDir() && fiDest != nil) || (!fiSrc.IsDir() && fiDest != nil && fiDest.IsDir()) { + destPath = filepath.Join(destPath, filepath.Base(src)) + } + + target := filepath.Dir(destPath) + + if copyDirContents && fiSrc.IsDir() && fiDest == nil { + target = destPath + } + if err := MkdirAll(target, 0755, c.chown, c.utime); err != nil { + return "", err + } + + return destPath, nil +} + +type ChownOpt struct { + Uid, Gid int +} + +type XAttrErrorHandler func(dst, src, xattrKey string, err error) error + +type CopyInfo struct { + Chown *ChownOpt + Utime *time.Time + AllowWildcards bool + Mode *int + XAttrErrorHandler XAttrErrorHandler + CopyDirContents bool + FollowLinks bool +} + +type Opt func(*CopyInfo) + +func WithCopyInfo(ci CopyInfo) func(*CopyInfo) { + return func(c *CopyInfo) { + *c = ci + } +} + +func WithChown(uid, gid int) Opt { + return func(ci *CopyInfo) { + ci.Chown = &ChownOpt{Uid: uid, Gid: gid} + } +} + +func AllowWildcards(ci *CopyInfo) { + ci.AllowWildcards = true +} + +func WithXAttrErrorHandler(h XAttrErrorHandler) Opt { + return func(ci *CopyInfo) { + ci.XAttrErrorHandler = h + } +} + +func AllowXAttrErrors(ci *CopyInfo) { + h := func(string, string, string, error) error { + return nil + } + WithXAttrErrorHandler(h)(ci) +} + +type copier struct { + chown *ChownOpt + utime *time.Time + mode *int + inodes map[uint64]string + xattrErrorHandler XAttrErrorHandler +} + +func newCopier(chown *ChownOpt, tm *time.Time, mode *int, xeh XAttrErrorHandler) *copier { + if xeh == nil { + xeh = func(dst, src, key string, err error) error { + return err + } + } + return &copier{inodes: map[uint64]string{}, chown: chown, utime: tm, xattrErrorHandler: xeh, mode: mode} +} + +// dest is always clean +func (c *copier) copy(ctx context.Context, src, target string, overwriteTargetMetadata bool) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + fi, err := os.Lstat(src) + if err != nil { + return errors.Wrapf(err, "failed to stat %s", src) + } + + if !fi.IsDir() { + if err := ensureEmptyFileTarget(target); err != nil { + return err + } + } + + copyFileInfo := true + + switch { + case fi.IsDir(): + if created, err := c.copyDirectory(ctx, src, target, fi, overwriteTargetMetadata); err != nil { + return err + } else if !overwriteTargetMetadata { + copyFileInfo = created + } + case (fi.Mode() & os.ModeType) == 0: + link, err := getLinkSource(target, fi, c.inodes) + if err != nil { + return errors.Wrap(err, "failed to get hardlink") + } + if link != "" { + if err := os.Link(link, target); err != nil { + return errors.Wrap(err, "failed to create hard link") + } + } else if err := copyFile(src, target); err != nil { + return errors.Wrap(err, "failed to copy files") + } + case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink: + link, err := os.Readlink(src) + if err != nil { + return errors.Wrapf(err, "failed to read link: %s", src) + } + if err := os.Symlink(link, target); err != nil { + return errors.Wrapf(err, "failed to create symlink: %s", target) + } + case (fi.Mode() & os.ModeDevice) == os.ModeDevice: + if err := copyDevice(target, fi); err != nil { + return errors.Wrapf(err, "failed to create device") + } + default: + // TODO: Support pipes and sockets + return errors.Wrapf(err, "unsupported mode %s", fi.Mode()) + } + + if copyFileInfo { + if err := c.copyFileInfo(fi, target); err != nil { + return errors.Wrap(err, "failed to copy file info") + } + + if err := copyXAttrs(target, src, c.xattrErrorHandler); err != nil { + return errors.Wrap(err, "failed to copy xattrs") + } + } + return nil +} + +func (c *copier) copyDirectory(ctx context.Context, src, dst string, stat os.FileInfo, overwriteTargetMetadata bool) (bool, error) { + if !stat.IsDir() { + return false, errors.Errorf("source is not directory") + } + + created := false + + if st, err := os.Lstat(dst); err != nil { + if !os.IsNotExist(err) { + return false, err + } + created = true + if err := os.Mkdir(dst, stat.Mode()); err != nil { + return created, errors.Wrapf(err, "failed to mkdir %s", dst) + } + } else if !st.IsDir() { + return false, errors.Errorf("cannot copy to non-directory: %s", dst) + } else if overwriteTargetMetadata { + if err := os.Chmod(dst, stat.Mode()); err != nil { + return false, errors.Wrapf(err, "failed to chmod on %s", dst) + } + } + + fis, err := ioutil.ReadDir(src) + if err != nil { + return false, errors.Wrapf(err, "failed to read %s", src) + } + + for _, fi := range fis { + if err := c.copy(ctx, filepath.Join(src, fi.Name()), filepath.Join(dst, fi.Name()), true); err != nil { + return false, err + } + } + + return created, nil +} + +func ensureEmptyFileTarget(dst string) error { + fi, err := os.Lstat(dst) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return errors.Wrap(err, "failed to lstat file target") + } + if fi.IsDir() { + return errors.Errorf("cannot replace to directory %s with file", dst) + } + return os.Remove(dst) +} + +func copyFile(source, target string) error { + src, err := os.Open(source) + if err != nil { + return errors.Wrapf(err, "failed to open source %s", source) + } + defer src.Close() + tgt, err := os.Create(target) + if err != nil { + return errors.Wrapf(err, "failed to open target %s", target) + } + defer tgt.Close() + + return copyFileContent(tgt, src) +} + +func containsWildcards(name string) bool { + isWindows := runtime.GOOS == "windows" + for i := 0; i < len(name); i++ { + ch := name[i] + if ch == '\\' && !isWindows { + i++ + } else if ch == '*' || ch == '?' || ch == '[' { + return true + } + } + return false +} + +func splitWildcards(p string) (d1, d2 string) { + parts := strings.Split(filepath.Join(p), string(filepath.Separator)) + var p1, p2 []string + var found bool + for _, p := range parts { + if !found && containsWildcards(p) { + found = true + } + if p == "" { + p = "/" + } + if !found { + p1 = append(p1, p) + } else { + p2 = append(p2, p) + } + } + return filepath.Join(p1...), filepath.Join(p2...) +} + +func resolveWildcards(basePath, comp string) ([]string, error) { + var out []string + err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + rel, err := rel(basePath, path) + if err != nil { + return err + } + if rel == "." { + return nil + } + if match, _ := filepath.Match(comp, rel); !match { + return nil + } + out = append(out, path) + if info.IsDir() { + return filepath.SkipDir + } + return nil + }) + if err != nil { + return nil, err + } + return out, nil +} + +// rel makes a path relative to base path. Same as `filepath.Rel` but can also +// handle UUID paths in windows. +func rel(basepath, targpath string) (string, error) { + // filepath.Rel can't handle UUID paths in windows + if runtime.GOOS == "windows" { + pfx := basepath + `\` + if strings.HasPrefix(targpath, pfx) { + p := strings.TrimPrefix(targpath, pfx) + if p == "" { + p = "." + } + return p, nil + } + } + return filepath.Rel(basepath, targpath) +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go b/vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go new file mode 100644 index 00000000..94b023d1 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go @@ -0,0 +1,97 @@ +package fs + +import ( + "io" + "math" + "os" + "syscall" + + "github.com/containerd/containerd/sys" + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func getUidGid(fi os.FileInfo) (uid, gid int) { + st := fi.Sys().(*syscall.Stat_t) + return int(st.Uid), int(st.Gid) +} + +func (c *copier) copyFileInfo(fi os.FileInfo, name string) error { + st := fi.Sys().(*syscall.Stat_t) + + chown := c.chown + if chown == nil { + uid, gid := getUidGid(fi) + chown = &ChownOpt{Uid: uid, Gid: gid} + } + if err := Chown(name, chown); err != nil { + return errors.Wrapf(err, "failed to chown %s", name) + } + + m := fi.Mode() + if c.mode != nil { + m = (m & ^os.FileMode(0777)) | os.FileMode(*c.mode&0777) + } + if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink { + if err := os.Chmod(name, m); err != nil { + return errors.Wrapf(err, "failed to chmod %s", name) + } + } + + if c.utime != nil { + if err := Utimes(name, c.utime); err != nil { + return err + } + } else { + timespec := []unix.Timespec{unix.Timespec(sys.StatAtime(st)), unix.Timespec(sys.StatMtime(st))} + if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil { + return errors.Wrapf(err, "failed to utime %s", name) + } + } + + return nil +} + +func copyFileContent(dst, src *os.File) error { + st, err := src.Stat() + if err != nil { + return errors.Wrap(err, "unable to stat source") + } + + var written int64 + size := st.Size() + first := true + + for written < size { + var desired int + if size-written > math.MaxInt32 { + desired = int(math.MaxInt32) + } else { + desired = int(size - written) + } + + n, err := unix.CopyFileRange(int(src.Fd()), nil, int(dst.Fd()), nil, desired, 0) + if err != nil { + if (err != unix.ENOSYS && err != unix.EXDEV && err != unix.EPERM) || !first { + return errors.Wrap(err, "copy file range failed") + } + + buf := bufferPool.Get().(*[]byte) + _, err = io.CopyBuffer(dst, src, *buf) + bufferPool.Put(buf) + return errors.Wrap(err, "userspace copy failed") + } + + first = false + written += int64(n) + } + return nil +} + +func copyDevice(dst string, fi os.FileInfo) error { + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return errors.New("unsupported stat type") + } + return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev)) +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go b/vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go new file mode 100644 index 00000000..cbd784e5 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go @@ -0,0 +1,28 @@ +// +build !windows + +package fs + +import ( + "github.com/pkg/errors" + + "github.com/containerd/continuity/sysx" +) + +// copyXAttrs requires xeh to be non-nil +func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error { + xattrKeys, err := sysx.LListxattr(src) + if err != nil { + return xeh(dst, src, "", errors.Wrapf(err, "failed to list xattrs on %s", src)) + } + for _, xattr := range xattrKeys { + data, err := sysx.LGetxattr(src, xattr) + if err != nil { + return xeh(dst, src, xattr, errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)) + } + if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil { + return xeh(dst, src, xattr, errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)) + } + } + + return nil +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go b/vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go new file mode 100644 index 00000000..f80b7dd8 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go @@ -0,0 +1,68 @@ +// +build solaris darwin freebsd + +package fs + +import ( + "io" + "os" + "syscall" + + "github.com/containerd/containerd/sys" + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func getUidGid(fi os.FileInfo) (uid, gid int) { + st := fi.Sys().(*syscall.Stat_t) + return int(st.Uid), int(st.Gid) +} + +func (c *copier) copyFileInfo(fi os.FileInfo, name string) error { + st := fi.Sys().(*syscall.Stat_t) + chown := c.chown + if chown == nil { + uid, gid := getUidGid(fi) + chown = &ChownOpt{Uid: uid, Gid: gid} + } + if err := Chown(name, chown); err != nil { + return errors.Wrapf(err, "failed to chown %s", name) + } + + m := fi.Mode() + if c.mode != nil { + m = (m & ^os.FileMode(0777)) | os.FileMode(*c.mode&0777) + } + if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink { + if err := os.Chmod(name, m); err != nil { + return errors.Wrapf(err, "failed to chmod %s", name) + } + } + + if c.utime != nil { + if err := Utimes(name, c.utime); err != nil { + return err + } + } else { + timespec := []unix.Timespec{unix.Timespec(sys.StatAtime(st)), unix.Timespec(sys.StatMtime(st))} + if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil { + return errors.Wrapf(err, "failed to utime %s", name) + } + } + return nil +} + +func copyFileContent(dst, src *os.File) error { + buf := bufferPool.Get().(*[]byte) + _, err := io.CopyBuffer(dst, src, *buf) + bufferPool.Put(buf) + + return err +} + +func copyDevice(dst string, fi os.FileInfo) error { + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return errors.New("unsupported stat type") + } + return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev)) +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go b/vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go new file mode 100644 index 00000000..1c6abb59 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go @@ -0,0 +1,33 @@ +package fs + +import ( + "io" + "os" + + "github.com/pkg/errors" +) + +func (c *copier) copyFileInfo(fi os.FileInfo, name string) error { + if err := os.Chmod(name, fi.Mode()); err != nil { + return errors.Wrapf(err, "failed to chmod %s", name) + } + + // TODO: copy windows specific metadata + + return nil +} + +func copyFileContent(dst, src *os.File) error { + buf := bufferPool.Get().(*[]byte) + _, err := io.CopyBuffer(dst, src, *buf) + bufferPool.Put(buf) + return err +} + +func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error { + return nil +} + +func copyDevice(dst string, fi os.FileInfo) error { + return errors.New("device copy not supported") +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/hardlink.go b/vendor/github.com/tonistiigi/fsutil/copy/hardlink.go new file mode 100644 index 00000000..38da9381 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/hardlink.go @@ -0,0 +1,27 @@ +package fs + +import "os" + +// GetLinkInfo returns an identifier representing the node a hardlink is pointing +// to. If the file is not hard linked then 0 will be returned. +func GetLinkInfo(fi os.FileInfo) (uint64, bool) { + return getLinkInfo(fi) +} + +// getLinkSource returns a path for the given name and +// file info to its link source in the provided inode +// map. If the given file name is not in the map and +// has other links, it is added to the inode map +// to be a source for other link locations. +func getLinkSource(name string, fi os.FileInfo, inodes map[uint64]string) (string, error) { + inode, isHardlink := getLinkInfo(fi) + if !isHardlink { + return "", nil + } + + path, ok := inodes[inode] + if !ok { + inodes[inode] = name + } + return path, nil +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go b/vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go new file mode 100644 index 00000000..3b825c94 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go @@ -0,0 +1,17 @@ +// +build !windows + +package fs + +import ( + "os" + "syscall" +) + +func getLinkInfo(fi os.FileInfo) (uint64, bool) { + s, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + return 0, false + } + + return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1 +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go b/vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go new file mode 100644 index 00000000..ad8845a7 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go @@ -0,0 +1,7 @@ +package fs + +import "os" + +func getLinkInfo(fi os.FileInfo) (uint64, bool) { + return 0, false +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/mkdir.go b/vendor/github.com/tonistiigi/fsutil/copy/mkdir.go new file mode 100644 index 00000000..649551f1 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/mkdir.go @@ -0,0 +1,74 @@ +package fs + +import ( + "os" + "syscall" + "time" +) + +func Chown(p string, user *ChownOpt) error { + if user != nil { + if err := os.Lchown(p, user.Uid, user.Gid); err != nil { + return err + } + } + return nil +} + +// MkdirAll is forked os.MkdirAll +func MkdirAll(path string, perm os.FileMode, user *ChownOpt, tm *time.Time) error { + // Fast path: if we can tell whether path is a directory or file, stop with success or error. + dir, err := os.Stat(path) + if err == nil { + if dir.IsDir() { + return nil + } + return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} + } + + // Slow path: make sure parent exists and then call Mkdir for path. + i := len(path) + for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator. + i-- + } + + j := i + for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element. + j-- + } + + if j > 1 { + // Create parent. + err = MkdirAll(fixRootDirectory(path[:j-1]), perm, user, tm) + if err != nil { + return err + } + } + + dir, err1 := os.Lstat(path) + if err1 == nil && dir.IsDir() { + return nil + } + + // Parent now exists; invoke Mkdir and use its result. + err = os.Mkdir(path, perm) + if err != nil { + // Handle arguments like "foo/." by + // double-checking that directory doesn't exist. + dir, err1 := os.Lstat(path) + if err1 == nil && dir.IsDir() { + return nil + } + return err + } + + if err := Chown(path, user); err != nil { + return err + } + + if err := Utimes(path, tm); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go b/vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go new file mode 100644 index 00000000..8fb0f6bc --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go @@ -0,0 +1,32 @@ +// +build !windows + +package fs + +import ( + "time" + + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func fixRootDirectory(p string) string { + return p +} + +func Utimes(p string, tm *time.Time) error { + if tm == nil { + return nil + } + + ts, err := unix.TimeToTimespec(*tm) + if err != nil { + return err + } + + timespec := []unix.Timespec{ts, ts} + if err := unix.UtimesNanoAt(unix.AT_FDCWD, p, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil { + return errors.Wrapf(err, "failed to utime %s", p) + } + + return nil +} diff --git a/vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go b/vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go new file mode 100644 index 00000000..6bd17e81 --- /dev/null +++ b/vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go @@ -0,0 +1,21 @@ +// +build windows + +package fs + +import ( + "os" + "time" +) + +func fixRootDirectory(p string) string { + if len(p) == len(`\\?\c:`) { + if os.IsPathSeparator(p[0]) && os.IsPathSeparator(p[1]) && p[2] == '?' && os.IsPathSeparator(p[3]) && p[5] == ':' { + return p + `\` + } + } + return p +} + +func Utimes(p string, tm *time.Time) error { + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 422203d2..74eb3f40 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -210,9 +210,10 @@ github.com/stretchr/testify/require github.com/stretchr/testify/assert # github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8 github.com/syndtr/gocapability/capability -# github.com/tonistiigi/fsutil v0.0.0-20190314220245-1ec1983587cd +# github.com/tonistiigi/fsutil v0.0.0-20190316003333-2a10686c7e92 github.com/tonistiigi/fsutil github.com/tonistiigi/fsutil/types +github.com/tonistiigi/fsutil/copy # github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea github.com/tonistiigi/units # github.com/uber/jaeger-client-go v0.0.0-20180103221425-e02c85f9069e