github.com/prysmaticlabs/prysm@v1.4.4/shared/fileutil/fileutil.go (about)

     1  package fileutil
     2  
     3  import (
     4  	"crypto/sha256"
     5  	"encoding/base64"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"os/user"
    11  	"path"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  
    16  	"github.com/pkg/errors"
    17  	"github.com/prysmaticlabs/prysm/shared/params"
    18  	log "github.com/sirupsen/logrus"
    19  )
    20  
    21  // ExpandPath given a string which may be a relative path.
    22  // 1. replace tilde with users home dir
    23  // 2. expands embedded environment variables
    24  // 3. cleans the path, e.g. /a/b/../c -> /a/c
    25  // Note, it has limitations, e.g. ~someuser/tmp will not be expanded
    26  func ExpandPath(p string) (string, error) {
    27  	if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
    28  		if home := HomeDir(); home != "" {
    29  			p = home + p[1:]
    30  		}
    31  	}
    32  	return filepath.Abs(path.Clean(os.ExpandEnv(p)))
    33  }
    34  
    35  // HandleBackupDir takes an input directory path and either alters its permissions to be usable if it already exists, creates it if not
    36  func HandleBackupDir(dirPath string, permissionOverride bool) error {
    37  	expanded, err := ExpandPath(dirPath)
    38  	if err != nil {
    39  		return err
    40  	}
    41  	exists, err := HasDir(expanded)
    42  	if err != nil {
    43  		return err
    44  	}
    45  	if exists {
    46  		info, err := os.Stat(expanded)
    47  		if err != nil {
    48  			return err
    49  		}
    50  		if info.Mode().Perm() != params.BeaconIoConfig().ReadWriteExecutePermissions {
    51  			if permissionOverride {
    52  				if err := os.Chmod(expanded, params.BeaconIoConfig().ReadWriteExecutePermissions); err != nil {
    53  					return err
    54  				}
    55  			} else {
    56  				return errors.New("dir already exists without proper 0700 permissions")
    57  			}
    58  		}
    59  	}
    60  	return os.MkdirAll(expanded, params.BeaconIoConfig().ReadWriteExecutePermissions)
    61  }
    62  
    63  // MkdirAll takes in a path, expands it if necessary, and looks through the
    64  // permissions of every directory along the path, ensuring we are not attempting
    65  // to overwrite any existing permissions. Finally, creates the directory accordingly
    66  // with standardized, Prysm project permissions. This is the static-analysis enforced
    67  // method for creating a directory programmatically in Prysm.
    68  func MkdirAll(dirPath string) error {
    69  	expanded, err := ExpandPath(dirPath)
    70  	if err != nil {
    71  		return err
    72  	}
    73  	exists, err := HasDir(expanded)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	if exists {
    78  		info, err := os.Stat(expanded)
    79  		if err != nil {
    80  			return err
    81  		}
    82  		if info.Mode().Perm() != params.BeaconIoConfig().ReadWriteExecutePermissions {
    83  			return errors.New("dir already exists without proper 0700 permissions")
    84  		}
    85  	}
    86  	return os.MkdirAll(expanded, params.BeaconIoConfig().ReadWriteExecutePermissions)
    87  }
    88  
    89  // WriteFile is the static-analysis enforced method for writing binary data to a file
    90  // in Prysm, enforcing a single entrypoint with standardized permissions.
    91  func WriteFile(file string, data []byte) error {
    92  	expanded, err := ExpandPath(file)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	if FileExists(expanded) {
    97  		info, err := os.Stat(expanded)
    98  		if err != nil {
    99  			return err
   100  		}
   101  		if info.Mode() != params.BeaconIoConfig().ReadWritePermissions {
   102  			return errors.New("file already exists without proper 0600 permissions")
   103  		}
   104  	}
   105  	return ioutil.WriteFile(expanded, data, params.BeaconIoConfig().ReadWritePermissions)
   106  }
   107  
   108  // HomeDir for a user.
   109  func HomeDir() string {
   110  	if home := os.Getenv("HOME"); home != "" {
   111  		return home
   112  	}
   113  	if usr, err := user.Current(); err == nil {
   114  		return usr.HomeDir
   115  	}
   116  	return ""
   117  }
   118  
   119  // HasDir checks if a directory indeed exists at the specified path.
   120  func HasDir(dirPath string) (bool, error) {
   121  	fullPath, err := ExpandPath(dirPath)
   122  	if err != nil {
   123  		return false, err
   124  	}
   125  	info, err := os.Stat(fullPath)
   126  	if os.IsNotExist(err) {
   127  		return false, nil
   128  	}
   129  	if info == nil {
   130  		return false, err
   131  	}
   132  	return info.IsDir(), err
   133  }
   134  
   135  // HasReadWritePermissions checks if file at a path has proper
   136  // 0600 permissions set.
   137  func HasReadWritePermissions(itemPath string) (bool, error) {
   138  	info, err := os.Stat(itemPath)
   139  	if err != nil {
   140  		return false, err
   141  	}
   142  	return info.Mode() == params.BeaconIoConfig().ReadWritePermissions, nil
   143  }
   144  
   145  // FileExists returns true if a file is not a directory and exists
   146  // at the specified path.
   147  func FileExists(filename string) bool {
   148  	filePath, err := ExpandPath(filename)
   149  	if err != nil {
   150  		return false
   151  	}
   152  	info, err := os.Stat(filePath)
   153  	if err != nil {
   154  		if !os.IsNotExist(err) {
   155  			log.WithError(err).Info("Checking for file existence returned an error")
   156  		}
   157  		return false
   158  	}
   159  	return info != nil && !info.IsDir()
   160  }
   161  
   162  // RecursiveFileFind returns true, and the path,  if a file is not a directory and exists
   163  // at  dir or any of its subdirectories.  Finds the first instant based on the Walk order and returns.
   164  // Define non-fatal error to stop the recursive directory walk
   165  var stopWalk = errors.New("stop walking")
   166  
   167  // RecursiveFileFind searches for file in a directory and its subdirectories.
   168  func RecursiveFileFind(filename, dir string) (bool, string, error) {
   169  	var found bool
   170  	var fpath string
   171  	dir = filepath.Clean(dir)
   172  	found = false
   173  	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   174  		if err != nil {
   175  			return err
   176  		}
   177  		// checks if its a file  and has the exact name as the filename
   178  		// need to break the walk function by using a non-fatal error
   179  		if !info.IsDir() && filename == info.Name() {
   180  			found = true
   181  			fpath = path
   182  			return stopWalk
   183  		}
   184  
   185  		// no errors or file found
   186  		return nil
   187  	})
   188  	if err != nil && err != stopWalk {
   189  		return false, "", err
   190  	}
   191  	return found, fpath, nil
   192  }
   193  
   194  // ReadFileAsBytes expands a file name's absolute path and reads it as bytes from disk.
   195  func ReadFileAsBytes(filename string) ([]byte, error) {
   196  	filePath, err := ExpandPath(filename)
   197  	if err != nil {
   198  		return nil, errors.Wrap(err, "could not determine absolute path of password file")
   199  	}
   200  	return ioutil.ReadFile(filePath)
   201  }
   202  
   203  // CopyFile copy a file from source to destination path.
   204  func CopyFile(src, dst string) error {
   205  	if !FileExists(src) {
   206  		return errors.New("source file does not exist at provided path")
   207  	}
   208  	f, err := os.Open(src)
   209  	if err != nil {
   210  		return err
   211  	}
   212  	dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, params.BeaconIoConfig().ReadWritePermissions)
   213  	if err != nil {
   214  		return err
   215  	}
   216  	_, err = io.Copy(dstFile, f)
   217  	return err
   218  }
   219  
   220  // CopyDir copies contents of one directory into another, recursively.
   221  func CopyDir(src, dst string) error {
   222  	dstExists, err := HasDir(dst)
   223  	if err != nil {
   224  		return err
   225  	}
   226  	if dstExists {
   227  		return errors.New("destination directory already exists")
   228  	}
   229  	fds, err := ioutil.ReadDir(src)
   230  	if err != nil {
   231  		return err
   232  	}
   233  	if err := MkdirAll(dst); err != nil {
   234  		return errors.Wrapf(err, "error creating directory: %s", dst)
   235  	}
   236  	for _, fd := range fds {
   237  		srcPath := path.Join(src, fd.Name())
   238  		dstPath := path.Join(dst, fd.Name())
   239  		if fd.IsDir() {
   240  			if err = CopyDir(srcPath, dstPath); err != nil {
   241  				return errors.Wrapf(err, "error copying directory %s -> %s", srcPath, dstPath)
   242  			}
   243  		} else {
   244  			if err = CopyFile(srcPath, dstPath); err != nil {
   245  				return errors.Wrapf(err, "error copying file %s -> %s", srcPath, dstPath)
   246  			}
   247  		}
   248  	}
   249  	return nil
   250  }
   251  
   252  // DirsEqual checks whether two directories have the same content.
   253  func DirsEqual(src, dst string) bool {
   254  	hash1, err := HashDir(src)
   255  	if err != nil {
   256  		return false
   257  	}
   258  
   259  	hash2, err := HashDir(dst)
   260  	if err != nil {
   261  		return false
   262  	}
   263  
   264  	return hash1 == hash2
   265  }
   266  
   267  // HashDir calculates and returns hash of directory: each file's hash is calculated and saved along
   268  // with the file name into the list, after which list is hashed to produce the final signature.
   269  // Implementation is based on https://github.com/golang/mod/blob/release-branch.go1.15/sumdb/dirhash/hash.go
   270  func HashDir(dir string) (string, error) {
   271  	files, err := DirFiles(dir)
   272  	if err != nil {
   273  		return "", err
   274  	}
   275  
   276  	h := sha256.New()
   277  	files = append([]string(nil), files...)
   278  	sort.Strings(files)
   279  	for _, file := range files {
   280  		fd, err := os.Open(filepath.Join(dir, file))
   281  		if err != nil {
   282  			return "", err
   283  		}
   284  		hf := sha256.New()
   285  		_, err = io.Copy(hf, fd)
   286  		if err != nil {
   287  			return "", err
   288  		}
   289  		if err := fd.Close(); err != nil {
   290  			return "", err
   291  		}
   292  		if _, err := fmt.Fprintf(h, "%x  %s\n", hf.Sum(nil), file); err != nil {
   293  			return "", err
   294  		}
   295  	}
   296  	return "hashdir:" + base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
   297  }
   298  
   299  // DirFiles returns list of files found within a given directory and its sub-directories.
   300  // Directory prefix will not be included as a part of returned file string i.e. for a file located
   301  // in "dir/foo/bar" only "foo/bar" part will be returned.
   302  func DirFiles(dir string) ([]string, error) {
   303  	var files []string
   304  	dir = filepath.Clean(dir)
   305  	err := filepath.Walk(dir, func(file string, info os.FileInfo, err error) error {
   306  		if err != nil {
   307  			return err
   308  		}
   309  		if info.IsDir() {
   310  			return nil
   311  		}
   312  		relFile := file
   313  		if dir != "." {
   314  			relFile = file[len(dir)+1:]
   315  		}
   316  		files = append(files, filepath.ToSlash(relFile))
   317  		return nil
   318  	})
   319  	if err != nil {
   320  		return nil, err
   321  	}
   322  	return files, nil
   323  }