github.com/paketo-buildpacks/packit@v1.3.2-0.20211206231111-86b75c657449/fs/checksum_calculator.go (about) 1 package fs 2 3 import ( 4 "crypto/sha256" 5 "encoding/hex" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "runtime" 11 "sort" 12 ) 13 14 // ChecksumCalculator can be used to calculate the SHA256 checksum of a given file or 15 // directory. When given a directory, checksum calculation will be performed in 16 // parallel. 17 type ChecksumCalculator struct{} 18 19 // NewChecksumCalculator returns a new instance of a ChecksumCalculator. 20 func NewChecksumCalculator() ChecksumCalculator { 21 return ChecksumCalculator{} 22 } 23 24 type calculatedFile struct { 25 path string 26 checksum []byte 27 err error 28 } 29 30 // Sum returns a hex-encoded SHA256 checksum value of a file or directory given a path. 31 func (c ChecksumCalculator) Sum(paths ...string) (string, error) { 32 var files []string 33 for _, path := range paths { 34 err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 35 if err != nil { 36 return err 37 } 38 39 if info.Mode().IsRegular() { 40 files = append(files, path) 41 } 42 43 return nil 44 }) 45 if err != nil { 46 return "", fmt.Errorf("failed to calculate checksum: %w", err) 47 } 48 } 49 50 //Gather all checksums 51 var sums [][]byte 52 for _, f := range getParallelChecksums(files) { 53 if f.err != nil { 54 return "", fmt.Errorf("failed to calculate checksum: %w", f.err) 55 } 56 57 sums = append(sums, f.checksum) 58 } 59 60 if len(sums) == 1 { 61 return hex.EncodeToString(sums[0]), nil 62 } 63 64 hash := sha256.New() 65 for _, sum := range sums { 66 _, err := hash.Write(sum) 67 if err != nil { 68 return "", fmt.Errorf("failed to calculate checksum: %w", err) 69 } 70 } 71 return hex.EncodeToString(hash.Sum(nil)), nil 72 } 73 74 func getParallelChecksums(filesFromDir []string) []calculatedFile { 75 var checksumResults []calculatedFile 76 numFiles := len(filesFromDir) 77 files := make(chan string, numFiles) 78 calculatedFiles := make(chan calculatedFile, numFiles) 79 80 //Spawns workers 81 for i := 0; i < runtime.NumCPU(); i++ { 82 go fileChecksumer(files, calculatedFiles) 83 } 84 85 //Puts files in worker queue 86 for _, f := range filesFromDir { 87 files <- f 88 } 89 90 close(files) 91 92 //Pull all calculated files off of result queue 93 for i := 0; i < numFiles; i++ { 94 checksumResults = append(checksumResults, <-calculatedFiles) 95 } 96 97 //Sort calculated files for consistent checksuming 98 sort.Slice(checksumResults, func(i, j int) bool { 99 return checksumResults[i].path < checksumResults[j].path 100 }) 101 102 return checksumResults 103 } 104 105 func fileChecksumer(files chan string, calculatedFiles chan calculatedFile) { 106 for path := range files { 107 result := calculatedFile{path: path} 108 109 file, err := os.Open(path) 110 if err != nil { 111 result.err = err 112 calculatedFiles <- result 113 continue 114 } 115 116 hash := sha256.New() 117 _, err = io.Copy(hash, file) 118 if err != nil { 119 result.err = err 120 calculatedFiles <- result 121 continue 122 } 123 124 if err := file.Close(); err != nil { 125 result.err = err 126 calculatedFiles <- result 127 continue 128 } 129 130 result.checksum = hash.Sum(nil) 131 calculatedFiles <- result 132 } 133 }