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 }