github.hscsec.cn/openshift/source-to-image@v1.2.0/pkg/util/fs/fs.go (about) 1 package fs 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path" 10 "path/filepath" 11 "runtime" 12 "sync" 13 "time" 14 15 utillog "github.com/openshift/source-to-image/pkg/util/log" 16 17 s2ierr "github.com/openshift/source-to-image/pkg/errors" 18 ) 19 20 var log = utillog.StderrLog 21 22 // FileSystem allows STI to work with the file system and 23 // perform tasks such as creating and deleting directories 24 type FileSystem interface { 25 Chmod(file string, mode os.FileMode) error 26 Rename(from, to string) error 27 MkdirAll(dirname string) error 28 MkdirAllWithPermissions(dirname string, perm os.FileMode) error 29 Mkdir(dirname string) error 30 Exists(file string) bool 31 Copy(sourcePath, targetPath string, filesToIgnore map[string]string) error 32 CopyContents(sourcePath, targetPath string, filesToIgnore map[string]string) error 33 RemoveDirectory(dir string) error 34 CreateWorkingDirectory() (string, error) 35 Open(file string) (io.ReadCloser, error) 36 Create(file string) (io.WriteCloser, error) 37 WriteFile(file string, data []byte) error 38 ReadDir(string) ([]os.FileInfo, error) 39 Stat(string) (os.FileInfo, error) 40 Lstat(string) (os.FileInfo, error) 41 Walk(string, filepath.WalkFunc) error 42 Readlink(string) (string, error) 43 Symlink(string, string) error 44 KeepSymlinks(bool) 45 ShouldKeepSymlinks() bool 46 } 47 48 // NewFileSystem creates a new instance of the default FileSystem 49 // implementation 50 func NewFileSystem() FileSystem { 51 return &fs{ 52 fileModes: make(map[string]os.FileMode), 53 keepSymlinks: false, 54 } 55 } 56 57 type fs struct { 58 // on Windows, fileModes is used to track the UNIX file mode of every file we 59 // work with; m is used to synchronize access to fileModes. 60 fileModes map[string]os.FileMode 61 m sync.Mutex 62 keepSymlinks bool 63 } 64 65 // FileInfo is a struct which implements os.FileInfo. We use it (a) for test 66 // purposes, and (b) because we enrich the FileMode on Windows systems 67 type FileInfo struct { 68 FileName string 69 FileSize int64 70 FileMode os.FileMode 71 FileModTime time.Time 72 FileIsDir bool 73 FileSys interface{} 74 } 75 76 // Name retuns the filename of fi 77 func (fi *FileInfo) Name() string { 78 return fi.FileName 79 } 80 81 // Size returns the file size of fi 82 func (fi *FileInfo) Size() int64 { 83 return fi.FileSize 84 } 85 86 // Mode returns the file mode of fi 87 func (fi *FileInfo) Mode() os.FileMode { 88 return fi.FileMode 89 } 90 91 // ModTime returns the file modification time of fi 92 func (fi *FileInfo) ModTime() time.Time { 93 return fi.FileModTime 94 } 95 96 // IsDir returns true if fi refers to a directory 97 func (fi *FileInfo) IsDir() bool { 98 return fi.FileIsDir 99 } 100 101 // Sys returns the sys interface of fi 102 func (fi *FileInfo) Sys() interface{} { 103 return fi.FileSys 104 } 105 106 func copyFileInfo(src os.FileInfo) *FileInfo { 107 return &FileInfo{ 108 FileName: src.Name(), 109 FileSize: src.Size(), 110 FileMode: src.Mode(), 111 FileModTime: src.ModTime(), 112 FileIsDir: src.IsDir(), 113 FileSys: src.Sys(), 114 } 115 } 116 117 // Stat returns a FileInfo describing the named file. 118 func (h *fs) Stat(path string) (os.FileInfo, error) { 119 fi, err := os.Stat(path) 120 if runtime.GOOS == "windows" && err == nil { 121 fi = h.enrichFileInfo(path, fi) 122 } 123 return fi, err 124 } 125 126 // Lstat returns a FileInfo describing the named file (not following symlinks). 127 func (h *fs) Lstat(path string) (os.FileInfo, error) { 128 fi, err := os.Lstat(path) 129 if runtime.GOOS == "windows" && err == nil { 130 fi = h.enrichFileInfo(path, fi) 131 } 132 return fi, err 133 } 134 135 // ReadDir reads the directory named by dirname and returns a list of directory 136 // entries sorted by filename. 137 func (h *fs) ReadDir(path string) ([]os.FileInfo, error) { 138 fis, err := ioutil.ReadDir(path) 139 if runtime.GOOS == "windows" && err == nil { 140 h.enrichFileInfos(path, fis) 141 } 142 return fis, err 143 } 144 145 // Chmod sets the file mode 146 func (h *fs) Chmod(file string, mode os.FileMode) error { 147 err := os.Chmod(file, mode) 148 if runtime.GOOS == "windows" && err == nil { 149 h.m.Lock() 150 h.fileModes[file] = mode 151 h.m.Unlock() 152 return nil 153 } 154 return err 155 } 156 157 // Rename renames or moves a file 158 func (h *fs) Rename(from, to string) error { 159 return os.Rename(from, to) 160 } 161 162 // MkdirAll creates the directory and all its parents 163 func (h *fs) MkdirAll(dirname string) error { 164 return os.MkdirAll(dirname, 0700) 165 } 166 167 // MkdirAllWithPermissions creates the directory and all its parents with the provided permissions 168 func (h *fs) MkdirAllWithPermissions(dirname string, perm os.FileMode) error { 169 return os.MkdirAll(dirname, perm) 170 } 171 172 // Mkdir creates the specified directory 173 func (h *fs) Mkdir(dirname string) error { 174 return os.Mkdir(dirname, 0700) 175 } 176 177 // Exists determines whether the given file exists 178 func (h *fs) Exists(file string) bool { 179 _, err := h.Stat(file) 180 return err == nil 181 } 182 183 // Copy copies the source to a destination. 184 // If the source is a file, then the destination has to be a file as well, 185 // otherwise you will get an error. 186 // If the source is a directory, then the destination has to be a directory and 187 // we copy the content of the source directory to destination directory 188 // recursively. 189 func (h *fs) Copy(source string, dest string, filesToIgnore map[string]string) (err error) { 190 return doCopy(h, source, dest, filesToIgnore) 191 } 192 193 // KeepSymlinks configures fs to copy symlinks from src as symlinks to dst. 194 // Default behavior is to follow symlinks and copy files by content. 195 func (h *fs) KeepSymlinks(k bool) { 196 h.keepSymlinks = k 197 } 198 199 // ShouldKeepSymlinks is exported only due to the design of fs util package 200 // and how the tests are structured. It indicates whether the implementation 201 // should copy symlinks as symlinks or follow symlinks and copy by content. 202 func (h *fs) ShouldKeepSymlinks() bool { 203 return h.keepSymlinks 204 } 205 206 // If src is symlink and symlink copy has been enabled, copy as a symlink. 207 // Otherwise ignore symlink and let rest of the code follow the symlink 208 // and copy the content of the file 209 func handleSymlink(h FileSystem, source, dest string) (bool, error) { 210 lstatinfo, lstaterr := h.Lstat(source) 211 _, staterr := h.Stat(source) 212 if lstaterr == nil && 213 lstatinfo.Mode()&os.ModeSymlink != 0 { 214 if os.IsNotExist(staterr) { 215 log.V(5).Infof("(broken) L %q -> %q", source, dest) 216 } else if h.ShouldKeepSymlinks() { 217 log.V(5).Infof("L %q -> %q", source, dest) 218 } else { 219 // symlink not handled here, will copy the file content 220 return false, nil 221 } 222 linkdest, err := h.Readlink(source) 223 if err != nil { 224 return true, err 225 } 226 return true, h.Symlink(linkdest, dest) 227 } 228 // symlink not handled here, will copy the file content 229 return false, nil 230 } 231 232 func doCopy(h FileSystem, source, dest string, filesToIgnore map[string]string) error { 233 if handled, err := handleSymlink(h, source, dest); handled || err != nil { 234 return err 235 } 236 sourcefile, err := h.Open(source) 237 if err != nil { 238 return err 239 } 240 defer sourcefile.Close() 241 sourceinfo, err := h.Stat(source) 242 if err != nil { 243 return err 244 } 245 246 if sourceinfo.IsDir() { 247 _, ok := filesToIgnore[source] 248 if ok { 249 log.V(5).Infof("Directory %q ignored", source) 250 return nil 251 } 252 log.V(5).Infof("D %q -> %q", source, dest) 253 return h.CopyContents(source, dest, filesToIgnore) 254 } 255 256 destinfo, _ := h.Stat(dest) 257 if destinfo != nil && destinfo.IsDir() { 258 return fmt.Errorf("destination must be full path to a file, not directory") 259 } 260 destfile, err := h.Create(dest) 261 if err != nil { 262 return err 263 } 264 defer destfile.Close() 265 _, ok := filesToIgnore[source] 266 if ok { 267 log.V(5).Infof("File %q ignored", source) 268 return nil 269 } 270 log.V(5).Infof("F %q -> %q", source, dest) 271 if _, err := io.Copy(destfile, sourcefile); err != nil { 272 return err 273 } 274 275 return h.Chmod(dest, sourceinfo.Mode()) 276 } 277 278 // CopyContents copies the content of the source directory to a destination 279 // directory. 280 // If the destination directory does not exists, it will be created. 281 // The source directory itself will not be copied, only its content. If you 282 // want this behavior, the destination must include the source directory name. 283 // It will skip any files provided in filesToIgnore from being copied 284 func (h *fs) CopyContents(src string, dest string, filesToIgnore map[string]string) (err error) { 285 sourceinfo, err := h.Stat(src) 286 if err != nil { 287 return err 288 } 289 if err = os.MkdirAll(dest, sourceinfo.Mode()); err != nil { 290 return err 291 } 292 directory, err := os.Open(src) 293 if err != nil { 294 return err 295 } 296 defer directory.Close() 297 objects, err := directory.Readdir(-1) 298 if err != nil { 299 return err 300 } 301 302 for _, obj := range objects { 303 source := path.Join(src, obj.Name()) 304 destination := path.Join(dest, obj.Name()) 305 if err := h.Copy(source, destination, filesToIgnore); err != nil { 306 return err 307 } 308 } 309 return 310 } 311 312 // RemoveDirectory removes the specified directory and all its contents 313 func (h *fs) RemoveDirectory(dir string) error { 314 log.V(2).Infof("Removing directory '%s'", dir) 315 316 // HACK: If deleting a directory in windows, call out to the system to do the deletion 317 // TODO: Remove this workaround when we switch to go 1.7 -- os.RemoveAll should 318 // be fixed for Windows in that release. https://github.com/golang/go/issues/9606 319 if runtime.GOOS == "windows" { 320 command := exec.Command("cmd.exe", "/c", fmt.Sprintf("rd /s /q %s", dir)) 321 output, err := command.Output() 322 if err != nil { 323 log.Errorf("Error removing directory %q: %v %s", dir, err, string(output)) 324 return err 325 } 326 return nil 327 } 328 329 err := os.RemoveAll(dir) 330 if err != nil { 331 log.Errorf("Error removing directory '%s': %v", dir, err) 332 } 333 return err 334 } 335 336 // CreateWorkingDirectory creates a directory to be used for STI 337 func (h *fs) CreateWorkingDirectory() (directory string, err error) { 338 directory, err = ioutil.TempDir("", "s2i") 339 if err != nil { 340 return "", s2ierr.NewWorkDirError(directory, err) 341 } 342 343 return directory, err 344 } 345 346 // Open opens a file and returns a ReadCloser interface to that file 347 func (h *fs) Open(filename string) (io.ReadCloser, error) { 348 return os.Open(filename) 349 } 350 351 // Create creates a file and returns a WriteCloser interface to that file 352 func (h *fs) Create(filename string) (io.WriteCloser, error) { 353 return os.Create(filename) 354 } 355 356 // WriteFile opens a file and writes data to it, returning error if such 357 // occurred 358 func (h *fs) WriteFile(filename string, data []byte) error { 359 return ioutil.WriteFile(filename, data, 0700) 360 } 361 362 // Walk walks the file tree rooted at root, calling walkFn for each file or 363 // directory in the tree, including root. 364 func (h *fs) Walk(root string, walkFn filepath.WalkFunc) error { 365 wrapper := func(path string, info os.FileInfo, err error) error { 366 if runtime.GOOS == "windows" && err == nil { 367 info = h.enrichFileInfo(path, info) 368 } 369 return walkFn(path, info, err) 370 } 371 return filepath.Walk(root, wrapper) 372 } 373 374 // enrichFileInfo is used on Windows. It takes an os.FileInfo object, e.g. as 375 // returned by os.Stat, and enriches the OS-returned file mode with the "real" 376 // UNIX file mode, if we know what it is. 377 func (h *fs) enrichFileInfo(path string, fi os.FileInfo) os.FileInfo { 378 h.m.Lock() 379 if mode, ok := h.fileModes[path]; ok { 380 fi = copyFileInfo(fi) 381 fi.(*FileInfo).FileMode = mode 382 } 383 h.m.Unlock() 384 return fi 385 } 386 387 // enrichFileInfos is used on Windows. It takes an array of os.FileInfo 388 // objects, e.g. as returned by os.ReadDir, and for each file enriches the OS- 389 // returned file mode with the "real" UNIX file mode, if we know what it is. 390 func (h *fs) enrichFileInfos(root string, fis []os.FileInfo) { 391 h.m.Lock() 392 for i := range fis { 393 if mode, ok := h.fileModes[filepath.Join(root, fis[i].Name())]; ok { 394 fis[i] = copyFileInfo(fis[i]) 395 fis[i].(*FileInfo).FileMode = mode 396 } 397 } 398 h.m.Unlock() 399 } 400 401 // Readlink reads the destination of a symlink 402 func (h *fs) Readlink(name string) (string, error) { 403 return os.Readlink(name) 404 } 405 406 // Symlink creates a symlink at newname, pointing to oldname 407 func (h *fs) Symlink(oldname, newname string) error { 408 return os.Symlink(oldname, newname) 409 }