Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 40 additions & 5 deletions fs/copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,49 @@ var bufferPool = &sync.Pool{
},
}

// XAttrErrorHandlers transform a non-nil xattr error.
// Return nil to ignore an error.
// xattrKey can be empty for listxattr operation.
type XAttrErrorHandler func(dst, src, xattrKey string, err error) error

type copyDirOpts struct {
xeh XAttrErrorHandler
}

type CopyDirOpt func(*copyDirOpts) error

// WithXAttrErrorHandler allows specifying XAttrErrorHandler
// If nil XAttrErrorHandler is specified (default), CopyDir stops
// on a non-nil xattr error.
func WithXAttrErrorHandler(xeh XAttrErrorHandler) CopyDirOpt {
return func(o *copyDirOpts) error {
o.xeh = xeh
return nil
}
}

// WithAllowXAttrErrors allows ignoring xattr errors.
func WithAllowXAttrErrors() CopyDirOpt {
xeh := func(dst, src, xattrKey string, err error) error {
return nil
}
return WithXAttrErrorHandler(xeh)
}

// CopyDir copies the directory from src to dst.
// Most efficient copy of files is attempted.
func CopyDir(dst, src string) error {
func CopyDir(dst, src string, opts ...CopyDirOpt) error {
var o copyDirOpts
for _, opt := range opts {
if err := opt(&o); err != nil {
return err
}
}
inodes := map[uint64]string{}
return copyDirectory(dst, src, inodes)
return copyDirectory(dst, src, inodes, &o)
}

func copyDirectory(dst, src string, inodes map[uint64]string) error {
func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) error {
stat, err := os.Stat(src)
if err != nil {
return errors.Wrapf(err, "failed to stat %s", src)
Expand Down Expand Up @@ -75,7 +110,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string) error {

switch {
case fi.IsDir():
if err := copyDirectory(target, source, inodes); err != nil {
if err := copyDirectory(target, source, inodes, o); err != nil {
return err
}
continue
Expand Down Expand Up @@ -111,7 +146,7 @@ func copyDirectory(dst, src string, inodes map[uint64]string) error {
return errors.Wrap(err, "failed to copy file info")
}

if err := copyXAttrs(target, source); err != nil {
if err := copyXAttrs(target, source, o.xeh); err != nil {
return errors.Wrap(err, "failed to copy xattrs")
}
}
Expand Down
30 changes: 23 additions & 7 deletions fs/copy_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ func copyFileContent(dst, src *os.File) error {
dstFd := int(dst.Fd())

for size > 0 {
// Ensure that we are never trying to copy more than SSIZE_MAX at a
// time and at the same time avoids overflows when the file is larger
// than 4GB on 32-bit systems.
// Ensure that we are never trying to copy more than SSIZE_MAX at a
// time and at the same time avoids overflows when the file is larger
// than 4GB on 32-bit systems.
var copySize int
if size > maxSSizeT {
copySize = int(maxSSizeT)
Expand All @@ -101,18 +101,34 @@ func copyFileContent(dst, src *os.File) error {
return nil
}

func copyXAttrs(dst, src string) error {
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
xattrKeys, err := sysx.LListxattr(src)
if err != nil {
return errors.Wrapf(err, "failed to list xattrs on %s", src)
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
if xeh != nil {
e = xeh(dst, src, "", e)
}
return e
}
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)
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
}

Expand Down
24 changes: 20 additions & 4 deletions fs/copy_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,34 @@ func copyFileContent(dst, src *os.File) error {
return err
}

func copyXAttrs(dst, src string) error {
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
xattrKeys, err := sysx.LListxattr(src)
if err != nil {
return errors.Wrapf(err, "failed to list xattrs on %s", src)
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
if xeh != nil {
e = xeh(dst, src, "", e)
}
return e
}
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)
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
if xeh != nil {
if e = xeh(dst, src, xattr, e); e == nil {
continue
}
}
return e
}
}

Expand Down
2 changes: 1 addition & 1 deletion fs/copy_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func copyFileContent(dst, src *os.File) error {
return err
}

func copyXAttrs(dst, src string) error {
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
return nil
}

Expand Down