github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/src/cmd/go/internal/cache/hash.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package cache 6 7 import ( 8 "bytes" 9 "crypto/sha256" 10 "fmt" 11 "hash" 12 "io" 13 "os" 14 "runtime" 15 "sync" 16 ) 17 18 var debugHash = false // set when GODEBUG=gocachehash=1 19 20 // HashSize is the number of bytes in a hash. 21 const HashSize = 32 22 23 // A Hash provides access to the canonical hash function used to index the cache. 24 // The current implementation uses salted SHA256, but clients must not assume this. 25 type Hash struct { 26 h hash.Hash 27 name string // for debugging 28 buf *bytes.Buffer // for verify 29 } 30 31 // hashSalt is a salt string added to the beginning of every hash 32 // created by NewHash. Using the Go version makes sure that different 33 // versions of the go command (or even different Git commits during 34 // work on the development branch) do not address the same cache 35 // entries, so that a bug in one version does not affect the execution 36 // of other versions. This salt will result in additional ActionID files 37 // in the cache, but not additional copies of the large output files, 38 // which are still addressed by unsalted SHA256. 39 var hashSalt = []byte(runtime.Version()) 40 41 // NewHash returns a new Hash. 42 // The caller is expected to Write data to it and then call Sum. 43 func NewHash(name string) *Hash { 44 h := &Hash{h: sha256.New(), name: name} 45 if debugHash { 46 fmt.Fprintf(os.Stderr, "HASH[%s]\n", h.name) 47 } 48 h.Write(hashSalt) 49 if verify { 50 h.buf = new(bytes.Buffer) 51 } 52 return h 53 } 54 55 // Write writes data to the running hash. 56 func (h *Hash) Write(b []byte) (int, error) { 57 if debugHash { 58 fmt.Fprintf(os.Stderr, "HASH[%s]: %q\n", h.name, b) 59 } 60 if h.buf != nil { 61 h.buf.Write(b) 62 } 63 return h.h.Write(b) 64 } 65 66 // Sum returns the hash of the data written previously. 67 func (h *Hash) Sum() [HashSize]byte { 68 var out [HashSize]byte 69 h.h.Sum(out[:0]) 70 if debugHash { 71 fmt.Fprintf(os.Stderr, "HASH[%s]: %x\n", h.name, out) 72 } 73 if h.buf != nil { 74 hashDebug.Lock() 75 if hashDebug.m == nil { 76 hashDebug.m = make(map[[HashSize]byte]string) 77 } 78 hashDebug.m[out] = h.buf.String() 79 hashDebug.Unlock() 80 } 81 return out 82 } 83 84 // In GODEBUG=gocacheverify=1 mode, 85 // hashDebug holds the input to every computed hash ID, 86 // so that we can work backward from the ID involved in a 87 // cache entry mismatch to a description of what should be there. 88 var hashDebug struct { 89 sync.Mutex 90 m map[[HashSize]byte]string 91 } 92 93 // reverseHash returns the input used to compute the hash id. 94 func reverseHash(id [HashSize]byte) string { 95 hashDebug.Lock() 96 s := hashDebug.m[id] 97 hashDebug.Unlock() 98 return s 99 } 100 101 var hashFileCache struct { 102 sync.Mutex 103 m map[string][HashSize]byte 104 } 105 106 // HashFile returns the hash of the named file. 107 // It caches repeated lookups for a given file, 108 // and the cache entry for a file can be initialized 109 // using SetFileHash. 110 // The hash used by FileHash is not the same as 111 // the hash used by NewHash. 112 func FileHash(file string) ([HashSize]byte, error) { 113 hashFileCache.Lock() 114 out, ok := hashFileCache.m[file] 115 hashFileCache.Unlock() 116 117 if ok { 118 return out, nil 119 } 120 121 h := sha256.New() 122 f, err := os.Open(file) 123 if err != nil { 124 if debugHash { 125 fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err) 126 } 127 return [HashSize]byte{}, err 128 } 129 _, err = io.Copy(h, f) 130 f.Close() 131 if err != nil { 132 if debugHash { 133 fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err) 134 } 135 return [HashSize]byte{}, err 136 } 137 h.Sum(out[:0]) 138 if debugHash { 139 fmt.Fprintf(os.Stderr, "HASH %s: %x\n", file, out) 140 } 141 142 SetFileHash(file, out) 143 return out, nil 144 } 145 146 // SetFileHash sets the hash returned by FileHash for file. 147 func SetFileHash(file string, sum [HashSize]byte) { 148 hashFileCache.Lock() 149 if hashFileCache.m == nil { 150 hashFileCache.m = make(map[string][HashSize]byte) 151 } 152 hashFileCache.m[file] = sum 153 hashFileCache.Unlock() 154 }