github.com/lazyboychen7/engine@v17.12.1-ce-rc2+incompatible/daemon/graphdriver/copy/copy.go (about) 1 // +build linux 2 3 package copy 4 5 /* 6 #include <linux/fs.h> 7 8 #ifndef FICLONE 9 #define FICLONE _IOW(0x94, 9, int) 10 #endif 11 */ 12 import "C" 13 import ( 14 "container/list" 15 "fmt" 16 "io" 17 "os" 18 "path/filepath" 19 "syscall" 20 "time" 21 22 "github.com/docker/docker/pkg/pools" 23 "github.com/docker/docker/pkg/system" 24 rsystem "github.com/opencontainers/runc/libcontainer/system" 25 "golang.org/x/sys/unix" 26 ) 27 28 // Mode indicates whether to use hardlink or copy content 29 type Mode int 30 31 const ( 32 // Content creates a new file, and copies the content of the file 33 Content Mode = iota 34 // Hardlink creates a new hardlink to the existing file 35 Hardlink 36 ) 37 38 func copyRegular(srcPath, dstPath string, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error { 39 srcFile, err := os.Open(srcPath) 40 if err != nil { 41 return err 42 } 43 defer srcFile.Close() 44 45 // If the destination file already exists, we shouldn't blow it away 46 dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, fileinfo.Mode()) 47 if err != nil { 48 return err 49 } 50 defer dstFile.Close() 51 52 if *copyWithFileClone { 53 _, _, err = unix.Syscall(unix.SYS_IOCTL, dstFile.Fd(), C.FICLONE, srcFile.Fd()) 54 if err == nil { 55 return nil 56 } 57 58 *copyWithFileClone = false 59 if err == unix.EXDEV { 60 *copyWithFileRange = false 61 } 62 } 63 if *copyWithFileRange { 64 err = doCopyWithFileRange(srcFile, dstFile, fileinfo) 65 // Trying the file_clone may not have caught the exdev case 66 // as the ioctl may not have been available (therefore EINVAL) 67 if err == unix.EXDEV || err == unix.ENOSYS { 68 *copyWithFileRange = false 69 } else { 70 return err 71 } 72 } 73 return legacyCopy(srcFile, dstFile) 74 } 75 76 func doCopyWithFileRange(srcFile, dstFile *os.File, fileinfo os.FileInfo) error { 77 amountLeftToCopy := fileinfo.Size() 78 79 for amountLeftToCopy > 0 { 80 n, err := unix.CopyFileRange(int(srcFile.Fd()), nil, int(dstFile.Fd()), nil, int(amountLeftToCopy), 0) 81 if err != nil { 82 return err 83 } 84 85 amountLeftToCopy = amountLeftToCopy - int64(n) 86 } 87 88 return nil 89 } 90 91 func legacyCopy(srcFile io.Reader, dstFile io.Writer) error { 92 _, err := pools.Copy(dstFile, srcFile) 93 94 return err 95 } 96 97 func copyXattr(srcPath, dstPath, attr string) error { 98 data, err := system.Lgetxattr(srcPath, attr) 99 if err != nil { 100 return err 101 } 102 if data != nil { 103 if err := system.Lsetxattr(dstPath, attr, data, 0); err != nil { 104 return err 105 } 106 } 107 return nil 108 } 109 110 type fileID struct { 111 dev uint64 112 ino uint64 113 } 114 115 type dirMtimeInfo struct { 116 dstPath *string 117 stat *syscall.Stat_t 118 } 119 120 // DirCopy copies or hardlinks the contents of one directory to another, 121 // properly handling xattrs, and soft links 122 // 123 // Copying xattrs can be opted out of by passing false for copyXattrs. 124 func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error { 125 copyWithFileRange := true 126 copyWithFileClone := true 127 128 // This is a map of source file inodes to dst file paths 129 copiedFiles := make(map[fileID]string) 130 131 dirsToSetMtimes := list.New() 132 err := filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error { 133 if err != nil { 134 return err 135 } 136 137 // Rebase path 138 relPath, err := filepath.Rel(srcDir, srcPath) 139 if err != nil { 140 return err 141 } 142 143 dstPath := filepath.Join(dstDir, relPath) 144 if err != nil { 145 return err 146 } 147 148 stat, ok := f.Sys().(*syscall.Stat_t) 149 if !ok { 150 return fmt.Errorf("Unable to get raw syscall.Stat_t data for %s", srcPath) 151 } 152 153 isHardlink := false 154 155 switch f.Mode() & os.ModeType { 156 case 0: // Regular file 157 id := fileID{dev: stat.Dev, ino: stat.Ino} 158 if copyMode == Hardlink { 159 isHardlink = true 160 if err2 := os.Link(srcPath, dstPath); err2 != nil { 161 return err2 162 } 163 } else if hardLinkDstPath, ok := copiedFiles[id]; ok { 164 if err2 := os.Link(hardLinkDstPath, dstPath); err2 != nil { 165 return err2 166 } 167 } else { 168 if err2 := copyRegular(srcPath, dstPath, f, ©WithFileRange, ©WithFileClone); err2 != nil { 169 return err2 170 } 171 copiedFiles[id] = dstPath 172 } 173 174 case os.ModeDir: 175 if err := os.Mkdir(dstPath, f.Mode()); err != nil && !os.IsExist(err) { 176 return err 177 } 178 179 case os.ModeSymlink: 180 link, err := os.Readlink(srcPath) 181 if err != nil { 182 return err 183 } 184 185 if err := os.Symlink(link, dstPath); err != nil { 186 return err 187 } 188 189 case os.ModeNamedPipe: 190 fallthrough 191 case os.ModeSocket: 192 if rsystem.RunningInUserNS() { 193 // cannot create a device if running in user namespace 194 return nil 195 } 196 if err := unix.Mkfifo(dstPath, stat.Mode); err != nil { 197 return err 198 } 199 200 case os.ModeDevice: 201 if err := unix.Mknod(dstPath, stat.Mode, int(stat.Rdev)); err != nil { 202 return err 203 } 204 205 default: 206 return fmt.Errorf("unknown file type for %s", srcPath) 207 } 208 209 // Everything below is copying metadata from src to dst. All this metadata 210 // already shares an inode for hardlinks. 211 if isHardlink { 212 return nil 213 } 214 215 if err := os.Lchown(dstPath, int(stat.Uid), int(stat.Gid)); err != nil { 216 return err 217 } 218 219 if copyXattrs { 220 if err := doCopyXattrs(srcPath, dstPath); err != nil { 221 return err 222 } 223 } 224 225 isSymlink := f.Mode()&os.ModeSymlink != 0 226 227 // There is no LChmod, so ignore mode for symlink. Also, this 228 // must happen after chown, as that can modify the file mode 229 if !isSymlink { 230 if err := os.Chmod(dstPath, f.Mode()); err != nil { 231 return err 232 } 233 } 234 235 // system.Chtimes doesn't support a NOFOLLOW flag atm 236 // nolint: unconvert 237 if f.IsDir() { 238 dirsToSetMtimes.PushFront(&dirMtimeInfo{dstPath: &dstPath, stat: stat}) 239 } else if !isSymlink { 240 aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) 241 mTime := time.Unix(int64(stat.Mtim.Sec), int64(stat.Mtim.Nsec)) 242 if err := system.Chtimes(dstPath, aTime, mTime); err != nil { 243 return err 244 } 245 } else { 246 ts := []syscall.Timespec{stat.Atim, stat.Mtim} 247 if err := system.LUtimesNano(dstPath, ts); err != nil { 248 return err 249 } 250 } 251 return nil 252 }) 253 if err != nil { 254 return err 255 } 256 for e := dirsToSetMtimes.Front(); e != nil; e = e.Next() { 257 mtimeInfo := e.Value.(*dirMtimeInfo) 258 ts := []syscall.Timespec{mtimeInfo.stat.Atim, mtimeInfo.stat.Mtim} 259 if err := system.LUtimesNano(*mtimeInfo.dstPath, ts); err != nil { 260 return err 261 } 262 } 263 264 return nil 265 } 266 267 func doCopyXattrs(srcPath, dstPath string) error { 268 if err := copyXattr(srcPath, dstPath, "security.capability"); err != nil { 269 return err 270 } 271 272 // We need to copy this attribute if it appears in an overlay upper layer, as 273 // this function is used to copy those. It is set by overlay if a directory 274 // is removed and then re-created and should not inherit anything from the 275 // same dir in the lower dir. 276 if err := copyXattr(srcPath, dstPath, "trusted.overlay.opaque"); err != nil { 277 return err 278 } 279 return nil 280 }