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