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