1/* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15*/ 16 17package fs 18 19import ( 20 "io" 21 "os" 22 "syscall" 23 24 "github.com/containerd/continuity/sysx" 25 "github.com/pkg/errors" 26 "golang.org/x/sys/unix" 27) 28 29func copyFileInfo(fi os.FileInfo, name string) error { 30 st := fi.Sys().(*syscall.Stat_t) 31 if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil { 32 if os.IsPermission(err) { 33 // Normally if uid/gid are the same this would be a no-op, but some 34 // filesystems may still return EPERM... for instance NFS does this. 35 // In such a case, this is not an error. 36 if dstStat, err2 := os.Lstat(name); err2 == nil { 37 st2 := dstStat.Sys().(*syscall.Stat_t) 38 if st.Uid == st2.Uid && st.Gid == st2.Gid { 39 err = nil 40 } 41 } 42 } 43 if err != nil { 44 return errors.Wrapf(err, "failed to chown %s", name) 45 } 46 } 47 48 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink { 49 if err := os.Chmod(name, fi.Mode()); err != nil { 50 return errors.Wrapf(err, "failed to chmod %s", name) 51 } 52 } 53 54 timespec := []unix.Timespec{unix.Timespec(StatAtime(st)), unix.Timespec(StatMtime(st))} 55 if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil { 56 return errors.Wrapf(err, "failed to utime %s", name) 57 } 58 59 return nil 60} 61 62const maxSSizeT = int64(^uint(0) >> 1) 63 64func copyFileContent(dst, src *os.File) error { 65 st, err := src.Stat() 66 if err != nil { 67 return errors.Wrap(err, "unable to stat source") 68 } 69 70 size := st.Size() 71 first := true 72 srcFd := int(src.Fd()) 73 dstFd := int(dst.Fd()) 74 75 for size > 0 { 76 // Ensure that we are never trying to copy more than SSIZE_MAX at a 77 // time and at the same time avoids overflows when the file is larger 78 // than 4GB on 32-bit systems. 79 var copySize int 80 if size > maxSSizeT { 81 copySize = int(maxSSizeT) 82 } else { 83 copySize = int(size) 84 } 85 n, err := unix.CopyFileRange(srcFd, nil, dstFd, nil, copySize, 0) 86 if err != nil { 87 if (err != unix.ENOSYS && err != unix.EXDEV) || !first { 88 return errors.Wrap(err, "copy file range failed") 89 } 90 91 buf := bufferPool.Get().(*[]byte) 92 _, err = io.CopyBuffer(dst, src, *buf) 93 bufferPool.Put(buf) 94 return errors.Wrap(err, "userspace copy failed") 95 } 96 97 first = false 98 size -= int64(n) 99 } 100 101 return nil 102} 103 104func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error { 105 xattrKeys, err := sysx.LListxattr(src) 106 if err != nil { 107 e := errors.Wrapf(err, "failed to list xattrs on %s", src) 108 if xeh != nil { 109 e = xeh(dst, src, "", e) 110 } 111 return e 112 } 113 for _, xattr := range xattrKeys { 114 data, err := sysx.LGetxattr(src, xattr) 115 if err != nil { 116 e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src) 117 if xeh != nil { 118 if e = xeh(dst, src, xattr, e); e == nil { 119 continue 120 } 121 } 122 return e 123 } 124 if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil { 125 e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst) 126 if xeh != nil { 127 if e = xeh(dst, src, xattr, e); e == nil { 128 continue 129 } 130 } 131 return e 132 } 133 } 134 135 return nil 136} 137 138func copyDevice(dst string, fi os.FileInfo) error { 139 st, ok := fi.Sys().(*syscall.Stat_t) 140 if !ok { 141 return errors.New("unsupported stat type") 142 } 143 return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev)) 144} 145