github.com/kubesphere/s2irun@v3.2.1+incompatible/pkg/utils/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 utilglog "github.com/kubesphere/s2irun/pkg/utils/glog" 16 17 s2ierr "github.com/kubesphere/s2irun/pkg/errors" 18 ) 19 20 var glog = utilglog.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) error 32 CopyContents(sourcePath, targetPath 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) (err error) { 190 return doCopy(h, source, dest) 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 glog.V(5).Infof("(broken) L %q -> %q", source, dest) 216 } else if h.ShouldKeepSymlinks() { 217 glog.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) 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 glog.V(5).Infof("D %q -> %q", source, dest) 248 return h.CopyContents(source, dest) 249 } 250 251 destinfo, _ := h.Stat(dest) 252 if destinfo != nil && destinfo.IsDir() { 253 return fmt.Errorf("destination must be full path to a file, not directory") 254 } 255 destfile, err := h.Create(dest) 256 if err != nil { 257 return err 258 } 259 defer destfile.Close() 260 glog.V(5).Infof("F %q -> %q", source, dest) 261 if _, err := io.Copy(destfile, sourcefile); err != nil { 262 return err 263 } 264 265 return h.Chmod(dest, sourceinfo.Mode()) 266 } 267 268 // CopyContents copies the content of the source directory to a destination 269 // directory. 270 // If the destination directory does not exists, it will be created. 271 // The source directory itself will not be copied, only its content. If you 272 // want this behavior, the destination must include the source directory name. 273 func (h *fs) CopyContents(src string, dest string) (err error) { 274 sourceinfo, err := h.Stat(src) 275 if err != nil { 276 return err 277 } 278 if err = os.MkdirAll(dest, sourceinfo.Mode()); err != nil { 279 return err 280 } 281 directory, err := os.Open(src) 282 if err != nil { 283 return err 284 } 285 defer directory.Close() 286 objects, err := directory.Readdir(-1) 287 if err != nil { 288 return err 289 } 290 for _, obj := range objects { 291 source := path.Join(src, obj.Name()) 292 destination := path.Join(dest, obj.Name()) 293 if err := h.Copy(source, destination); err != nil { 294 return err 295 } 296 } 297 return 298 } 299 300 // RemoveDirectory removes the specified directory and all its contents 301 func (h *fs) RemoveDirectory(dir string) error { 302 glog.V(2).Infof("Removing directory '%s'", dir) 303 304 // HACK: If deleting a directory in windows, call out to the system to do the deletion 305 // TODO: Remove this workaround when we switch to go 1.7 -- os.RemoveAll should 306 // be fixed for Windows in that release. https://github.com/golang/go/issues/9606 307 if runtime.GOOS == "windows" { 308 command := exec.Command("cmd.exe", "/c", fmt.Sprintf("rd /s /q %s", dir)) 309 output, err := command.Output() 310 if err != nil { 311 glog.Errorf("Error removing directory %q: %v %s", dir, err, string(output)) 312 return err 313 } 314 return nil 315 } 316 317 err := os.RemoveAll(dir) 318 if err != nil { 319 glog.Errorf("Error removing directory '%s': %v", dir, err) 320 } 321 return err 322 } 323 324 // CreateWorkingDirectory creates a directory to be used for STI 325 func (h *fs) CreateWorkingDirectory() (directory string, err error) { 326 directory, err = ioutil.TempDir("", "s2i") 327 if err != nil { 328 return "", s2ierr.NewWorkDirError(directory, err) 329 } 330 331 return directory, err 332 } 333 334 // Open opens a file and returns a ReadCloser interface to that file 335 func (h *fs) Open(filename string) (io.ReadCloser, error) { 336 return os.Open(filename) 337 } 338 339 // Create creates a file and returns a WriteCloser interface to that file 340 func (h *fs) Create(filename string) (io.WriteCloser, error) { 341 return os.Create(filename) 342 } 343 344 // WriteFile opens a file and writes data to it, returning error if such 345 // occurred 346 func (h *fs) WriteFile(filename string, data []byte) error { 347 return ioutil.WriteFile(filename, data, 0700) 348 } 349 350 // Walk walks the file tree rooted at root, calling walkFn for each file or 351 // directory in the tree, including root. 352 func (h *fs) Walk(root string, walkFn filepath.WalkFunc) error { 353 wrapper := func(path string, info os.FileInfo, err error) error { 354 if runtime.GOOS == "windows" && err == nil { 355 info = h.enrichFileInfo(path, info) 356 } 357 return walkFn(path, info, err) 358 } 359 return filepath.Walk(root, wrapper) 360 } 361 362 // enrichFileInfo is used on Windows. It takes an os.FileInfo object, e.g. as 363 // returned by os.Stat, and enriches the OS-returned file mode with the "real" 364 // UNIX file mode, if we know what it is. 365 func (h *fs) enrichFileInfo(path string, fi os.FileInfo) os.FileInfo { 366 h.m.Lock() 367 if mode, ok := h.fileModes[path]; ok { 368 fi = copyFileInfo(fi) 369 fi.(*FileInfo).FileMode = mode 370 } 371 h.m.Unlock() 372 return fi 373 } 374 375 // enrichFileInfos is used on Windows. It takes an array of os.FileInfo 376 // objects, e.g. as returned by os.ReadDir, and for each file enriches the OS- 377 // returned file mode with the "real" UNIX file mode, if we know what it is. 378 func (h *fs) enrichFileInfos(root string, fis []os.FileInfo) { 379 h.m.Lock() 380 for i := range fis { 381 if mode, ok := h.fileModes[filepath.Join(root, fis[i].Name())]; ok { 382 fis[i] = copyFileInfo(fis[i]) 383 fis[i].(*FileInfo).FileMode = mode 384 } 385 } 386 h.m.Unlock() 387 } 388 389 // Readlink reads the destination of a symlink 390 func (h *fs) Readlink(name string) (string, error) { 391 return os.Readlink(name) 392 } 393 394 // Symlink creates a symlink at newname, pointing to oldname 395 func (h *fs) Symlink(oldname, newname string) error { 396 return os.Symlink(oldname, newname) 397 }