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