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  }