hash.go (4340B)
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 "sync" 15 ) 16 17 var debugHash = false // set when GODEBUG=gocachehash=1 18 19 // HashSize is the number of bytes in a hash. 20 const HashSize = 32 21 22 // hashSalt is an optional salt prepended to every hash created by NewHash. 23 // When set (via SetSalt), it is typically the build ID of the current binary, 24 // so that cache entries are invalidated when the binary changes. 25 var hashSalt []byte 26 27 // A Hash provides access to the canonical hash function used to index the cache. 28 // The current implementation uses salted SHA256, but clients must not assume this. 29 type Hash struct { 30 h hash.Hash 31 name string // for debugging 32 buf *bytes.Buffer // for verify 33 } 34 35 // Subkey returns an action ID corresponding to mixing a parent 36 // action ID with a string description of the subkey. 37 func Subkey(parent ActionID, desc string) ActionID { 38 h := sha256.New() 39 h.Write([]byte("subkey:")) 40 h.Write(parent[:]) 41 h.Write([]byte(desc)) 42 var out ActionID 43 h.Sum(out[:0]) 44 if debugHash { 45 fmt.Fprintf(os.Stderr, "HASH subkey %x %q = %x\n", parent, desc, out) 46 } 47 if verify { 48 hashDebug.Lock() 49 hashDebug.m[out] = fmt.Sprintf("subkey %x %q", parent, desc) 50 hashDebug.Unlock() 51 } 52 return out 53 } 54 55 // SetSalt sets an optional salt that is prepended to every hash created by 56 // NewHash. This is typically the build ID of the current binary, ensuring that 57 // cache entries are invalidated when the binary changes. If not called, 58 // NewHash operates without a salt. 59 func SetSalt(b []byte) { 60 hashSalt = b 61 } 62 63 // NewHash returns a new Hash. 64 // The caller is expected to Write data to it and then call Sum. 65 func NewHash(name string) *Hash { 66 h := &Hash{h: sha256.New(), name: name} 67 if debugHash { 68 fmt.Fprintf(os.Stderr, "HASH[%s]\n", h.name) 69 } 70 h.Write(hashSalt) 71 if verify { 72 h.buf = new(bytes.Buffer) 73 } 74 return h 75 } 76 77 // Write writes data to the running hash. 78 func (h *Hash) Write(b []byte) (int, error) { 79 if debugHash { 80 fmt.Fprintf(os.Stderr, "HASH[%s]: %q\n", h.name, b) 81 } 82 if h.buf != nil { 83 h.buf.Write(b) 84 } 85 return h.h.Write(b) 86 } 87 88 // Sum returns the hash of the data written previously. 89 func (h *Hash) Sum() [HashSize]byte { 90 var out [HashSize]byte 91 h.h.Sum(out[:0]) 92 if debugHash { 93 fmt.Fprintf(os.Stderr, "HASH[%s]: %x\n", h.name, out) 94 } 95 if h.buf != nil { 96 hashDebug.Lock() 97 if hashDebug.m == nil { 98 hashDebug.m = make(map[[HashSize]byte]string) 99 } 100 hashDebug.m[out] = h.buf.String() 101 hashDebug.Unlock() 102 } 103 return out 104 } 105 106 // In GODEBUG=gocacheverify=1 mode, 107 // hashDebug holds the input to every computed hash ID, 108 // so that we can work backward from the ID involved in a 109 // cache entry mismatch to a description of what should be there. 110 var hashDebug struct { 111 sync.Mutex 112 m map[[HashSize]byte]string 113 } 114 115 // reverseHash returns the input used to compute the hash id. 116 func reverseHash(id [HashSize]byte) string { 117 hashDebug.Lock() 118 s := hashDebug.m[id] 119 hashDebug.Unlock() 120 return s 121 } 122 123 var hashFileCache struct { 124 sync.Mutex 125 m map[string][HashSize]byte 126 } 127 128 // FileHash returns the hash of the named file. 129 // It caches repeated lookups for a given file, 130 // and the cache entry for a file can be initialized 131 // using SetFileHash. 132 // The hash used by FileHash is not the same as 133 // the hash used by NewHash. 134 func FileHash(file string) ([HashSize]byte, error) { 135 hashFileCache.Lock() 136 out, ok := hashFileCache.m[file] 137 hashFileCache.Unlock() 138 139 if ok { 140 return out, nil 141 } 142 143 h := sha256.New() 144 f, err := os.Open(file) 145 if err != nil { 146 if debugHash { 147 fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err) 148 } 149 return [HashSize]byte{}, err 150 } 151 _, err = io.Copy(h, f) 152 f.Close() 153 if err != nil { 154 if debugHash { 155 fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err) 156 } 157 return [HashSize]byte{}, err 158 } 159 h.Sum(out[:0]) 160 if debugHash { 161 fmt.Fprintf(os.Stderr, "HASH %s: %x\n", file, out) 162 } 163 164 SetFileHash(file, out) 165 return out, nil 166 } 167 168 // SetFileHash sets the hash returned by FileHash for file. 169 func SetFileHash(file string, sum [HashSize]byte) { 170 hashFileCache.Lock() 171 if hashFileCache.m == nil { 172 hashFileCache.m = make(map[string][HashSize]byte) 173 } 174 hashFileCache.m[file] = sum 175 hashFileCache.Unlock() 176 }