mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-22 19:28:08 +08:00
config: fix file/folder ownership
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
691
vendor/github.com/tonistiigi/fsutil/copy/copy.go
generated
vendored
Normal file
691
vendor/github.com/tonistiigi/fsutil/copy/copy.go
generated
vendored
Normal file
@@ -0,0 +1,691 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/continuity/fs"
|
||||
"github.com/moby/patternmatcher"
|
||||
"github.com/pkg/errors"
|
||||
mode "github.com/tonistiigi/dchapes-mode"
|
||||
"github.com/tonistiigi/fsutil"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
var modeSet *mode.Set
|
||||
if ci.ModeStr != "" {
|
||||
ms, err := mode.ParseWithUmask(ci.ModeStr, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
modeSet = &ms
|
||||
}
|
||||
|
||||
dst, err := fs.RootPath(dstRoot, filepath.Clean(dst))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := newCopier(dstRoot, ci.Chown, ci.Utime, ci.Mode, modeSet, ci.XAttrErrorHandler, ci.IncludePatterns, ci.ExcludePatterns, ci.AlwaysReplaceExistingDestPaths, ci.ChangeFunc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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, patternmatcher.MatchInfo{}, patternmatcher.MatchInfo{}); 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 User struct {
|
||||
UID, GID int
|
||||
SID string
|
||||
}
|
||||
|
||||
type Chowner func(*User) (*User, error)
|
||||
|
||||
type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
|
||||
|
||||
type CopyInfo struct {
|
||||
Chown Chowner
|
||||
Utime *time.Time
|
||||
AllowWildcards bool
|
||||
Mode *int
|
||||
// ModeStr is mode in non-octal format. Overrides Mode if non-empty.
|
||||
ModeStr string
|
||||
XAttrErrorHandler XAttrErrorHandler
|
||||
CopyDirContents bool
|
||||
FollowLinks bool
|
||||
// Include only files/dirs matching at least one of these patterns
|
||||
IncludePatterns []string
|
||||
// Exclude files/dir matching any of these patterns (even if they match an include pattern)
|
||||
ExcludePatterns []string
|
||||
// If true, any source path that overwrite existing destination paths will always replace
|
||||
// the existing destination path, even if they are of different types (e.g. a directory will
|
||||
// replace any existing symlink or file)
|
||||
AlwaysReplaceExistingDestPaths bool
|
||||
ChangeFunc fsutil.ChangeFunc
|
||||
}
|
||||
|
||||
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 = func(*User) (*User, error) {
|
||||
return &User{UID: uid, GID: gid}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func WithIncludePattern(includePattern string) Opt {
|
||||
return func(ci *CopyInfo) {
|
||||
ci.IncludePatterns = append(ci.IncludePatterns, includePattern)
|
||||
}
|
||||
}
|
||||
|
||||
func WithExcludePattern(excludePattern string) Opt {
|
||||
return func(ci *CopyInfo) {
|
||||
ci.ExcludePatterns = append(ci.ExcludePatterns, excludePattern)
|
||||
}
|
||||
}
|
||||
|
||||
func WithChangeNotifier(fn fsutil.ChangeFunc) Opt {
|
||||
return func(ci *CopyInfo) {
|
||||
ci.ChangeFunc = fn
|
||||
}
|
||||
}
|
||||
|
||||
type copier struct {
|
||||
chown Chowner
|
||||
utime *time.Time
|
||||
mode *int
|
||||
modeSet *mode.Set
|
||||
inodes map[uint64]string
|
||||
xattrErrorHandler XAttrErrorHandler
|
||||
includePatternMatcher *patternmatcher.PatternMatcher
|
||||
excludePatternMatcher *patternmatcher.PatternMatcher
|
||||
parentDirs []parentDir
|
||||
changefn fsutil.ChangeFunc
|
||||
root string
|
||||
alwaysReplaceExistingDestPaths bool
|
||||
}
|
||||
|
||||
type parentDir struct {
|
||||
srcPath string
|
||||
dstPath string
|
||||
copied bool
|
||||
}
|
||||
|
||||
func newCopier(root string, chown Chowner, tm *time.Time, mode *int, modeSet *mode.Set, xeh XAttrErrorHandler, includePatterns, excludePatterns []string, alwaysReplaceExistingDestPaths bool, changeFunc fsutil.ChangeFunc) (*copier, error) {
|
||||
if xeh == nil {
|
||||
xeh = func(dst, src, key string, err error) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var includePatternMatcher *patternmatcher.PatternMatcher
|
||||
if len(includePatterns) != 0 {
|
||||
var err error
|
||||
includePatternMatcher, err = patternmatcher.New(includePatterns)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid includepatterns: %s", includePatterns)
|
||||
}
|
||||
}
|
||||
|
||||
var excludePatternMatcher *patternmatcher.PatternMatcher
|
||||
if len(excludePatterns) != 0 {
|
||||
var err error
|
||||
excludePatternMatcher, err = patternmatcher.New(excludePatterns)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid excludepatterns: %s", excludePatterns)
|
||||
}
|
||||
}
|
||||
|
||||
return &copier{
|
||||
root: root,
|
||||
inodes: map[uint64]string{},
|
||||
chown: chown,
|
||||
utime: tm,
|
||||
xattrErrorHandler: xeh,
|
||||
mode: mode,
|
||||
modeSet: modeSet,
|
||||
includePatternMatcher: includePatternMatcher,
|
||||
excludePatternMatcher: excludePatternMatcher,
|
||||
changefn: changeFunc,
|
||||
alwaysReplaceExistingDestPaths: alwaysReplaceExistingDestPaths,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// dest is always clean
|
||||
func (c *copier) copy(ctx context.Context, src, srcComponents, target string, overwriteTargetMetadata bool, parentIncludeMatchInfo, parentExcludeMatchInfo patternmatcher.MatchInfo) 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)
|
||||
}
|
||||
targetFi, err := os.Lstat(target)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return errors.Wrapf(err, "failed to stat %s", src)
|
||||
}
|
||||
|
||||
include := true
|
||||
var (
|
||||
includeMatchInfo patternmatcher.MatchInfo
|
||||
excludeMatchInfo patternmatcher.MatchInfo
|
||||
)
|
||||
if srcComponents != "" {
|
||||
matchesIncludePattern := false
|
||||
matchesExcludePattern := false
|
||||
matchesIncludePattern, includeMatchInfo, err = c.include(srcComponents, fi, parentIncludeMatchInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
include = matchesIncludePattern
|
||||
|
||||
matchesExcludePattern, excludeMatchInfo, err = c.exclude(srcComponents, fi, parentExcludeMatchInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if matchesExcludePattern {
|
||||
include = false
|
||||
}
|
||||
}
|
||||
|
||||
if include {
|
||||
if err := c.removeTargetIfNeeded(src, target, fi, targetFi); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.createParentDirs(src, srcComponents, target, overwriteTargetMetadata); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
if !include {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := ensureEmptyFileTarget(target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
copyFileInfo := include
|
||||
restoreFileTimestamp := false
|
||||
notify := true
|
||||
|
||||
switch {
|
||||
case fi.IsDir():
|
||||
if created, err := c.copyDirectory(
|
||||
ctx, src, srcComponents, target, fi, overwriteTargetMetadata,
|
||||
include, includeMatchInfo, excludeMatchInfo,
|
||||
); err != nil {
|
||||
return err
|
||||
} else if !overwriteTargetMetadata {
|
||||
// if we aren't supposed to overwrite existing target metadata,
|
||||
// then we only need to copy the new file info if we newly created
|
||||
// it, or restore the previous file timestamp if not
|
||||
copyFileInfo = created
|
||||
restoreFileTimestamp = !created
|
||||
}
|
||||
notify = false
|
||||
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,
|
||||
(fi.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe,
|
||||
(fi.Mode() & os.ModeSocket) == os.ModeSocket:
|
||||
if err := copyDevice(target, fi); err != nil {
|
||||
return errors.Wrapf(err, "failed to create device")
|
||||
}
|
||||
}
|
||||
|
||||
if copyFileInfo {
|
||||
if err := c.copyFileInfo(fi, src, 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")
|
||||
}
|
||||
} else if restoreFileTimestamp && targetFi != nil {
|
||||
if err := c.copyFileTimestamp(fi, target); err != nil {
|
||||
return errors.Wrap(err, "failed to restore file timestamp")
|
||||
}
|
||||
}
|
||||
if notify {
|
||||
if err := c.notifyChange(target, fi); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *copier) notifyChange(target string, fi os.FileInfo) error {
|
||||
if c.changefn != nil {
|
||||
if err := c.changefn(fsutil.ChangeKindAdd, path.Clean(strings.TrimPrefix(target, c.root)), fi, nil); err != nil {
|
||||
return errors.Wrap(err, "failed to notify file change")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *copier) include(path string, fi os.FileInfo, parentIncludeMatchInfo patternmatcher.MatchInfo) (bool, patternmatcher.MatchInfo, error) {
|
||||
if c.includePatternMatcher == nil {
|
||||
return true, patternmatcher.MatchInfo{}, nil
|
||||
}
|
||||
|
||||
m, matchInfo, err := c.includePatternMatcher.MatchesUsingParentResults(path, parentIncludeMatchInfo)
|
||||
if err != nil {
|
||||
return false, matchInfo, errors.Wrap(err, "failed to match includepatterns")
|
||||
}
|
||||
return m, matchInfo, nil
|
||||
}
|
||||
|
||||
func (c *copier) exclude(path string, fi os.FileInfo, parentExcludeMatchInfo patternmatcher.MatchInfo) (bool, patternmatcher.MatchInfo, error) {
|
||||
if c.excludePatternMatcher == nil {
|
||||
return false, patternmatcher.MatchInfo{}, nil
|
||||
}
|
||||
|
||||
m, matchInfo, err := c.excludePatternMatcher.MatchesUsingParentResults(path, parentExcludeMatchInfo)
|
||||
if err != nil {
|
||||
return false, matchInfo, errors.Wrap(err, "failed to match excludepatterns")
|
||||
}
|
||||
return m, matchInfo, nil
|
||||
}
|
||||
|
||||
func (c *copier) removeTargetIfNeeded(src, target string, srcFi, targetFi os.FileInfo) error {
|
||||
if !c.alwaysReplaceExistingDestPaths {
|
||||
return nil
|
||||
}
|
||||
if targetFi == nil {
|
||||
// already doesn't exist
|
||||
return nil
|
||||
}
|
||||
if srcFi.IsDir() && targetFi.IsDir() {
|
||||
// directories are merged, not replaced
|
||||
return nil
|
||||
}
|
||||
return os.RemoveAll(target)
|
||||
}
|
||||
|
||||
// Delayed creation of parent directories when a file or dir matches an include
|
||||
// pattern.
|
||||
func (c *copier) createParentDirs(src, srcComponents, target string, overwriteTargetMetadata bool) error {
|
||||
for i, parentDir := range c.parentDirs {
|
||||
if parentDir.copied {
|
||||
continue
|
||||
}
|
||||
|
||||
fi, err := os.Stat(parentDir.srcPath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to stat %s", src)
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return errors.Errorf("%s is not a directory", parentDir.srcPath)
|
||||
}
|
||||
|
||||
created, err := copyDirectoryOnly(parentDir.srcPath, parentDir.dstPath, fi, overwriteTargetMetadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if created {
|
||||
if err := c.copyFileInfo(fi, parentDir.srcPath, parentDir.dstPath); err != nil {
|
||||
return errors.Wrap(err, "failed to copy file info")
|
||||
}
|
||||
|
||||
if err := copyXAttrs(parentDir.dstPath, parentDir.srcPath, c.xattrErrorHandler); err != nil {
|
||||
return errors.Wrap(err, "failed to copy xattrs")
|
||||
}
|
||||
}
|
||||
|
||||
c.parentDirs[i].copied = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *copier) copyDirectory(
|
||||
ctx context.Context,
|
||||
src string,
|
||||
srcComponents string,
|
||||
dst string,
|
||||
stat os.FileInfo,
|
||||
overwriteTargetMetadata bool,
|
||||
include bool,
|
||||
includeMatchInfo patternmatcher.MatchInfo,
|
||||
excludeMatchInfo patternmatcher.MatchInfo,
|
||||
) (bool, error) {
|
||||
if !stat.IsDir() {
|
||||
return false, errors.Errorf("source is not directory")
|
||||
}
|
||||
|
||||
created := false
|
||||
|
||||
parentDir := parentDir{
|
||||
srcPath: src,
|
||||
dstPath: dst,
|
||||
}
|
||||
|
||||
// If this directory passed include/exclude matching directly, go ahead
|
||||
// and create the directory. Otherwise, delay to handle include
|
||||
// patterns like a/*/c where we do not want to create a/b until we
|
||||
// encounter a/b/c.
|
||||
if include {
|
||||
var err error
|
||||
created, err = copyDirectoryOnly(src, dst, stat, overwriteTargetMetadata)
|
||||
if err != nil {
|
||||
return created, err
|
||||
}
|
||||
if created || overwriteTargetMetadata {
|
||||
if err := c.notifyChange(dst, stat); err != nil {
|
||||
return created, err
|
||||
}
|
||||
}
|
||||
parentDir.copied = true
|
||||
}
|
||||
|
||||
c.parentDirs = append(c.parentDirs, parentDir)
|
||||
|
||||
defer func() {
|
||||
c.parentDirs = c.parentDirs[:len(c.parentDirs)-1]
|
||||
}()
|
||||
|
||||
fis, err := os.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(srcComponents, fi.Name()),
|
||||
filepath.Join(dst, fi.Name()),
|
||||
true, includeMatchInfo, excludeMatchInfo,
|
||||
); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func copyDirectoryOnly(src, dst string, stat os.FileInfo, overwriteTargetMetadata bool) (bool, error) {
|
||||
if st, err := os.Lstat(dst); err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return false, err
|
||||
}
|
||||
if err := os.Mkdir(dst, stat.Mode()); err != nil {
|
||||
return false, errors.Wrapf(err, "failed to mkdir %s", dst)
|
||||
}
|
||||
return true, nil
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
return false, 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 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)
|
||||
}
|
47
vendor/github.com/tonistiigi/fsutil/copy/copy_darwin.go
generated
vendored
Normal file
47
vendor/github.com/tonistiigi/fsutil/copy/copy_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func copyFile(source, target string) error {
|
||||
if err := unix.Clonefileat(unix.AT_FDCWD, source, unix.AT_FDCWD, target, unix.CLONE_NOFOLLOW); err != nil {
|
||||
if err != unix.EINVAL && err != unix.EXDEV {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 copyFileContent(dst, src *os.File) error {
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err := io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func mknod(dst string, mode uint32, rDev int) error {
|
||||
return unix.Mknod(dst, uint32(mode), rDev)
|
||||
}
|
38
vendor/github.com/tonistiigi/fsutil/copy/copy_freebsd.go
generated
vendored
Normal file
38
vendor/github.com/tonistiigi/fsutil/copy/copy_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
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 copyFileContent(dst, src *os.File) error {
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err := io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func mknod(dst string, mode uint32, rDev int) error {
|
||||
return unix.Mknod(dst, uint32(mode), uint64(rDev))
|
||||
}
|
129
vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go
generated
vendored
Normal file
129
vendor/github.com/tonistiigi/fsutil/copy/copy_linux.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"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, src, name string) error {
|
||||
chown := c.chown
|
||||
uid, gid := getUIDGID(fi)
|
||||
old := &User{UID: uid, GID: gid}
|
||||
if chown == nil {
|
||||
chown = func(u *User) (*User, error) {
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
if err := Chown(name, old, chown); err != nil {
|
||||
return errors.Wrapf(err, "failed to chown %s", name)
|
||||
}
|
||||
|
||||
m := fi.Mode()
|
||||
if c.modeSet != nil {
|
||||
m = c.modeSet.Apply(m)
|
||||
} else if c.mode != nil {
|
||||
m = os.FileMode(*c.mode).Perm()
|
||||
if *c.mode&syscall.S_ISGID != 0 {
|
||||
m |= os.ModeSetgid
|
||||
}
|
||||
if *c.mode&syscall.S_ISUID != 0 {
|
||||
m |= os.ModeSetuid
|
||||
}
|
||||
if *c.mode&syscall.S_ISVTX != 0 {
|
||||
m |= os.ModeSticky
|
||||
}
|
||||
}
|
||||
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 err := c.copyFileTimestamp(fi, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *copier) copyFileTimestamp(fi os.FileInfo, name string) error {
|
||||
if c.utime != nil {
|
||||
return Utimes(name, c.utime)
|
||||
}
|
||||
|
||||
st := fi.Sys().(*syscall.Stat_t)
|
||||
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 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 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 {
|
||||
// matches go/src/internal/poll/copy_file_range_linux.go
|
||||
if (err != unix.ENOSYS && err != unix.EXDEV && err != unix.EPERM && err != syscall.EIO && err != unix.EOPNOTSUPP && err != syscall.EINVAL) || !first {
|
||||
return errors.Wrap(err, "copy file range failed")
|
||||
}
|
||||
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err = io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "userspace copy failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
first = false
|
||||
written += int64(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mknod(dst string, mode uint32, rDev int) error {
|
||||
return unix.Mknod(dst, uint32(mode), rDev)
|
||||
}
|
46
vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go
generated
vendored
Normal file
46
vendor/github.com/tonistiigi/fsutil/copy/copy_nowindows.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
func copyDevice(dst string, fi os.FileInfo) error {
|
||||
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return errors.New("unsupported stat type")
|
||||
}
|
||||
var rDev int
|
||||
if fi.Mode()&os.ModeDevice == os.ModeDevice || fi.Mode()&os.ModeCharDevice == os.ModeCharDevice {
|
||||
rDev = int(st.Rdev)
|
||||
}
|
||||
mode := st.Mode
|
||||
mode &^= syscall.S_IFSOCK // socket copied as stub
|
||||
return mknod(dst, uint32(mode), rDev)
|
||||
}
|
70
vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go
generated
vendored
Normal file
70
vendor/github.com/tonistiigi/fsutil/copy/copy_unix.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
//go:build solaris || darwin || freebsd
|
||||
// +build solaris darwin freebsd
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"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, src, name string) error {
|
||||
chown := c.chown
|
||||
uid, gid := getUIDGID(fi)
|
||||
old := &User{UID: uid, GID: gid}
|
||||
if chown == nil {
|
||||
chown = func(u *User) (*User, error) {
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
if err := Chown(name, old, chown); err != nil {
|
||||
return errors.Wrapf(err, "failed to chown %s", name)
|
||||
}
|
||||
|
||||
m := fi.Mode()
|
||||
if c.modeSet != nil {
|
||||
m = c.modeSet.Apply(m)
|
||||
} else if c.mode != nil {
|
||||
m = os.FileMode(*c.mode).Perm()
|
||||
if *c.mode&syscall.S_ISGID != 0 {
|
||||
m |= os.ModeSetgid
|
||||
}
|
||||
if *c.mode&syscall.S_ISUID != 0 {
|
||||
m |= os.ModeSetuid
|
||||
}
|
||||
if *c.mode&syscall.S_ISVTX != 0 {
|
||||
m |= os.ModeSticky
|
||||
}
|
||||
}
|
||||
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 err := c.copyFileTimestamp(fi, name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *copier) copyFileTimestamp(fi os.FileInfo, name string) error {
|
||||
if c.utime != nil {
|
||||
return Utimes(name, c.utime)
|
||||
}
|
||||
|
||||
st := fi.Sys().(*syscall.Stat_t)
|
||||
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
|
||||
}
|
128
vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go
generated
vendored
Normal file
128
vendor/github.com/tonistiigi/fsutil/copy/copy_windows.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
seTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
|
||||
)
|
||||
|
||||
func getUIDGID(fi os.FileInfo) (uid, gid int) {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func getFileSecurityInfo(name string) (*windows.SID, *windows.ACL, error) {
|
||||
secInfo, err := windows.GetNamedSecurityInfo(
|
||||
name, windows.SE_FILE_OBJECT,
|
||||
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "fetching security info")
|
||||
}
|
||||
sid, _, err := secInfo.Owner()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "fetching owner SID")
|
||||
}
|
||||
dacl, _, err := secInfo.DACL()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "fetching dacl")
|
||||
}
|
||||
return sid, dacl, nil
|
||||
}
|
||||
|
||||
func (c *copier) copyFileInfo(fi os.FileInfo, src, name string) error {
|
||||
if c.modeSet != nil {
|
||||
return errors.Errorf("non-octal mode not supported on windows")
|
||||
}
|
||||
|
||||
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||
}
|
||||
|
||||
sid, dacl, err := getFileSecurityInfo(src)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting file info")
|
||||
}
|
||||
|
||||
if c.chown != nil {
|
||||
// Use the defined chowner.
|
||||
usr := &User{SID: sid.String()}
|
||||
if err := Chown(name, usr, c.chown); err != nil {
|
||||
return errors.Wrapf(err, "failed to chown %s", name)
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
// Copy file ownership and ACL from the source file.
|
||||
// We need SeRestorePrivilege and SeTakeOwnershipPrivilege in order
|
||||
// to restore security info on a file, especially if we're trying to
|
||||
// apply security info which includes SIDs not necessarily present on
|
||||
// the host.
|
||||
privileges := []string{winio.SeRestorePrivilege, seTakeOwnershipPrivilege}
|
||||
if err := winio.EnableProcessPrivileges(privileges); err != nil {
|
||||
return err
|
||||
}
|
||||
defer winio.DisableProcessPrivileges(privileges)
|
||||
|
||||
if err := windows.SetNamedSecurityInfo(
|
||||
name, windows.SE_FILE_OBJECT,
|
||||
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
|
||||
sid, nil, dacl, nil); err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.copyFileTimestamp(fi, name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *copier) copyFileTimestamp(fi os.FileInfo, name string) error {
|
||||
if c.utime != nil {
|
||||
return Utimes(name, c.utime)
|
||||
}
|
||||
|
||||
if fi.Mode()&os.ModeSymlink == 0 {
|
||||
if err := os.Chtimes(name, fi.ModTime(), fi.ModTime()); err != nil {
|
||||
return errors.Wrap(err, "changing mtime")
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
func copyFileContent(dst, src *os.File) error {
|
||||
buf := bufferPool.Get().(*[]byte)
|
||||
_, err := io.CopyBuffer(dst, src, *buf)
|
||||
bufferPool.Put(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDevice(dst string, fi os.FileInfo) error {
|
||||
return errors.New("device copy not supported")
|
||||
}
|
27
vendor/github.com/tonistiigi/fsutil/copy/hardlink.go
generated
vendored
Normal file
27
vendor/github.com/tonistiigi/fsutil/copy/hardlink.go
generated
vendored
Normal file
@@ -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
vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go
generated
vendored
Normal file
18
vendor/github.com/tonistiigi/fsutil/copy/hardlink_unix.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||
s, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1
|
||||
}
|
7
vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go
generated
vendored
Normal file
7
vendor/github.com/tonistiigi/fsutil/copy/hardlink_windows.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package fs
|
||||
|
||||
import "os"
|
||||
|
||||
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||
return 0, false
|
||||
}
|
65
vendor/github.com/tonistiigi/fsutil/copy/mkdir.go
generated
vendored
Normal file
65
vendor/github.com/tonistiigi/fsutil/copy/mkdir.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MkdirAll is forked os.MkdirAll
|
||||
func MkdirAll(path string, perm os.FileMode, user Chowner, 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, nil, user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Utimes(path, tm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
50
vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go
generated
vendored
Normal file
50
vendor/github.com/tonistiigi/fsutil/copy/mkdir_unix.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"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
|
||||
}
|
||||
|
||||
func Chown(p string, old *User, fn Chowner) error {
|
||||
if fn == nil {
|
||||
return nil
|
||||
}
|
||||
user, err := fn(old)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if user != nil {
|
||||
if err := os.Lchown(p, user.UID, user.GID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
103
vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go
generated
vendored
Normal file
103
vendor/github.com/tonistiigi/fsutil/copy/mkdir_windows.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
containerAdministratorSidString = "S-1-5-93-2-1"
|
||||
)
|
||||
|
||||
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 {
|
||||
info, err := os.Lstat(p)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fetching file info")
|
||||
}
|
||||
if tm != nil && info.Mode()&os.ModeSymlink == 0 {
|
||||
if err := os.Chtimes(p, *tm, *tm); err != nil {
|
||||
return errors.Wrap(err, "changing times")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Chown(p string, old *User, fn Chowner) error {
|
||||
if fn == nil {
|
||||
return nil
|
||||
}
|
||||
user, err := fn(old)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
var userSIDstring string
|
||||
if user != nil && user.SID != "" {
|
||||
userSIDstring = user.SID
|
||||
}
|
||||
if userSIDstring == "" {
|
||||
userSIDstring = containerAdministratorSidString
|
||||
|
||||
}
|
||||
|
||||
sidPtr, err := syscall.UTF16PtrFromString(userSIDstring)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "converting to utf16 ptr")
|
||||
}
|
||||
var userSID *windows.SID
|
||||
if err := windows.ConvertStringSidToSid(sidPtr, &userSID); err != nil {
|
||||
return errors.Wrap(err, "converting to windows SID")
|
||||
}
|
||||
var dacl *windows.ACL
|
||||
newEntries := []windows.EXPLICIT_ACCESS{
|
||||
{
|
||||
AccessPermissions: windows.GENERIC_ALL,
|
||||
AccessMode: windows.GRANT_ACCESS,
|
||||
Inheritance: windows.SUB_CONTAINERS_AND_OBJECTS_INHERIT,
|
||||
Trustee: windows.TRUSTEE{
|
||||
TrusteeForm: windows.TRUSTEE_IS_SID,
|
||||
TrusteeValue: windows.TrusteeValueFromSID(userSID),
|
||||
},
|
||||
},
|
||||
}
|
||||
newAcl, err := windows.ACLFromEntries(newEntries, dacl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("adding acls: %w", err)
|
||||
}
|
||||
|
||||
// Copy file ownership and ACL
|
||||
// We need SeRestorePrivilege and SeTakeOwnershipPrivilege in order
|
||||
// to restore security info on a file, especially if we're trying to
|
||||
// apply security info which includes SIDs not necessarily present on
|
||||
// the host.
|
||||
privileges := []string{winio.SeRestorePrivilege, seTakeOwnershipPrivilege}
|
||||
err = winio.RunWithPrivileges(privileges, func() error {
|
||||
if err := windows.SetNamedSecurityInfo(
|
||||
p, windows.SE_FILE_OBJECT,
|
||||
windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION,
|
||||
userSID, nil, newAcl, nil); err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
17
vendor/github.com/tonistiigi/fsutil/copy/stat_bsd.go
generated
vendored
Normal file
17
vendor/github.com/tonistiigi/fsutil/copy/stat_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// +build darwin freebsd netbsd openbsd
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Returns the last-accessed time
|
||||
func StatAtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Atimespec
|
||||
}
|
||||
|
||||
// Returns the last-modified time
|
||||
func StatMtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Mtimespec
|
||||
}
|
18
vendor/github.com/tonistiigi/fsutil/copy/stat_sysv.go
generated
vendored
Normal file
18
vendor/github.com/tonistiigi/fsutil/copy/stat_sysv.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build dragonfly || linux || solaris
|
||||
// +build dragonfly linux solaris
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Returns the last-accessed time
|
||||
func StatAtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Atim
|
||||
}
|
||||
|
||||
// Returns the last-modified time
|
||||
func StatMtime(st *syscall.Stat_t) syscall.Timespec {
|
||||
return st.Mtim
|
||||
}
|
Reference in New Issue
Block a user