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  }