github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/pkg/fileutil/fileutil.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fileutil 16 17 import ( 18 "fmt" 19 "io" 20 "os" 21 "path/filepath" 22 "syscall" 23 "time" 24 25 "github.com/rkt/rkt/pkg/user" 26 27 "github.com/appc/spec/pkg/device" 28 ) 29 30 func CopyRegularFile(src, dest string) (err error) { 31 srcFile, err := os.Open(src) 32 if err != nil { 33 return err 34 } 35 defer srcFile.Close() 36 destFile, err := os.Create(dest) 37 if err != nil { 38 return err 39 } 40 defer func() { 41 e := destFile.Close() 42 if err == nil { 43 err = e 44 } 45 }() 46 if _, err := io.Copy(destFile, srcFile); err != nil { 47 return err 48 } 49 return nil 50 } 51 52 func CopySymlink(src, dest string) error { 53 symTarget, err := os.Readlink(src) 54 if err != nil { 55 return err 56 } 57 if err := os.Symlink(symTarget, dest); err != nil { 58 return err 59 } 60 return nil 61 } 62 63 func CopyTree(src, dest string, uidRange *user.UidRange) error { 64 cleanSrc := filepath.Clean(src) 65 dirs := make(map[string][]syscall.Timespec) 66 copyWalker := func(path string, info os.FileInfo, err error) error { 67 if err != nil { 68 return err 69 } 70 rootLess := path[len(cleanSrc):] 71 if cleanSrc == "." { 72 rootLess = path 73 } 74 target := filepath.Join(dest, rootLess) 75 mode := info.Mode() 76 switch { 77 case mode.IsDir(): 78 err := os.Mkdir(target, mode.Perm()) 79 if err != nil { 80 return err 81 } 82 83 dir, err := os.Open(target) 84 if err != nil { 85 return err 86 } 87 if err := dir.Chmod(mode); err != nil { 88 dir.Close() 89 return err 90 } 91 dir.Close() 92 case mode.IsRegular(): 93 if err := CopyRegularFile(path, target); err != nil { 94 return err 95 } 96 case mode&os.ModeSymlink == os.ModeSymlink: 97 if err := CopySymlink(path, target); err != nil { 98 return err 99 } 100 case mode&os.ModeCharDevice == os.ModeCharDevice: 101 stat := syscall.Stat_t{} 102 if err := syscall.Stat(path, &stat); err != nil { 103 return err 104 } 105 106 dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev)) 107 mode := uint32(mode) | syscall.S_IFCHR 108 if err := syscall.Mknod(target, mode, int(dev)); err != nil { 109 return err 110 } 111 case mode&os.ModeDevice == os.ModeDevice: 112 stat := syscall.Stat_t{} 113 if err := syscall.Stat(path, &stat); err != nil { 114 return err 115 } 116 117 dev := device.Makedev(device.Major(stat.Rdev), device.Minor(stat.Rdev)) 118 mode := uint32(mode) | syscall.S_IFBLK 119 if err := syscall.Mknod(target, mode, int(dev)); err != nil { 120 return err 121 } 122 case mode&os.ModeNamedPipe == os.ModeNamedPipe: 123 if err := syscall.Mkfifo(target, uint32(mode)); err != nil { 124 return err 125 } 126 default: 127 return fmt.Errorf("unsupported mode: %v", mode) 128 } 129 130 var srcUid = info.Sys().(*syscall.Stat_t).Uid 131 var srcGid = info.Sys().(*syscall.Stat_t).Gid 132 133 shiftedUid, shiftedGid, err := uidRange.ShiftRange(srcUid, srcGid) 134 if err != nil { 135 return err 136 } 137 138 if err := os.Lchown(target, int(shiftedUid), int(shiftedGid)); err != nil { 139 return err 140 } 141 142 // lchown(2) says that, depending on the linux kernel version, it 143 // can change the file's mode also if executed as root. So call 144 // os.Chmod after it. 145 if mode&os.ModeSymlink != os.ModeSymlink { 146 if err := os.Chmod(target, mode); err != nil { 147 return err 148 } 149 } 150 151 ts, err := pathToTimespec(path) 152 if err != nil { 153 return err 154 } 155 156 if mode.IsDir() { 157 dirs[target] = ts 158 } 159 if mode&os.ModeSymlink != os.ModeSymlink { 160 if err := syscall.UtimesNano(target, ts); err != nil { 161 return err 162 } 163 } else { 164 if err := LUtimesNano(target, ts); err != nil { 165 return err 166 } 167 } 168 169 return nil 170 } 171 172 if err := filepath.Walk(cleanSrc, copyWalker); err != nil { 173 return err 174 } 175 176 // Restore dirs atime and mtime. This has to be done after copying 177 // as a file copying will change its parent directory's times. 178 for dirPath, ts := range dirs { 179 if err := syscall.UtimesNano(dirPath, ts); err != nil { 180 return err 181 } 182 } 183 184 return nil 185 } 186 187 func pathToTimespec(name string) ([]syscall.Timespec, error) { 188 fi, err := os.Lstat(name) 189 if err != nil { 190 return nil, err 191 } 192 mtime := fi.ModTime() 193 stat := fi.Sys().(*syscall.Stat_t) 194 atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) 195 return []syscall.Timespec{TimeToTimespec(atime), TimeToTimespec(mtime)}, nil 196 } 197 198 // TODO(sgotti) use UTIMES_OMIT on linux if Time.IsZero ? 199 func TimeToTimespec(time time.Time) (ts syscall.Timespec) { 200 nsec := int64(0) 201 if !time.IsZero() { 202 nsec = time.UnixNano() 203 } 204 return syscall.NsecToTimespec(nsec) 205 } 206 207 // DirSize takes a path and returns its size in bytes 208 func DirSize(path string) (int64, error) { 209 seenInode := make(map[uint64]struct{}) 210 211 if _, err := os.Stat(path); err == nil { 212 var sz int64 213 err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 214 if hasHardLinks(info) { 215 ino := getInode(info) 216 if _, ok := seenInode[ino]; !ok { 217 seenInode[ino] = struct{}{} 218 sz += info.Size() 219 } 220 } else { 221 sz += info.Size() 222 } 223 return err 224 }) 225 return sz, err 226 } 227 228 return 0, nil 229 } 230 231 // IsExecutable checks if the given path points to an executable file by 232 // checking the executable bit. Inspired by os.exec.LookPath() 233 func IsExecutable(path string) bool { 234 d, err := os.Stat(path) 235 if err == nil { 236 m := d.Mode() 237 return !m.IsDir() && m&0111 != 0 238 } 239 return false 240 } 241 242 // IsDeviceNode checks if the given path points to a block or char device. 243 // It doesn't follow symlinks. 244 func IsDeviceNode(path string) bool { 245 d, err := os.Lstat(path) 246 if err == nil { 247 m := d.Mode() 248 return m&os.ModeDevice == os.ModeDevice 249 } 250 return false 251 }