github.com/chenfeining/golangci-lint@v1.0.2-0.20230730162517-14c6c67868df/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 "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 // hashSalt is a salt string added to the beginning of every hash 31 // created by NewHash. Using the golangci-lint version makes sure that different 32 // versions of the command do not address the same cache 33 // entries, so that a bug in one version does not affect the execution 34 // of other versions. This salt will result in additional ActionID files 35 // in the cache, but not additional copies of the large output files, 36 // which are still addressed by unsalted SHA256. 37 var hashSalt []byte 38 39 func SetSalt(b []byte) { 40 hashSalt = b 41 } 42 43 // Subkey returns an action ID corresponding to mixing a parent 44 // action ID with a string description of the subkey. 45 func Subkey(parent ActionID, desc string) (ActionID, error) { 46 h := sha256.New() 47 const subkeyPrefix = "subkey:" 48 if n, err := h.Write([]byte(subkeyPrefix)); n != len(subkeyPrefix) { 49 return ActionID{}, fmt.Errorf("wrote %d/%d bytes of subkey prefix with error %s", n, len(subkeyPrefix), err) 50 } 51 if n, err := h.Write(parent[:]); n != len(parent) { 52 return ActionID{}, fmt.Errorf("wrote %d/%d bytes of parent with error %s", n, len(parent), err) 53 } 54 if n, err := h.Write([]byte(desc)); n != len(desc) { 55 return ActionID{}, fmt.Errorf("wrote %d/%d bytes of desc with error %s", n, len(desc), err) 56 } 57 58 var out ActionID 59 h.Sum(out[:0]) 60 if debugHash { 61 fmt.Fprintf(os.Stderr, "HASH subkey %x %q = %x\n", parent, desc, out) 62 } 63 if verify { 64 hashDebug.Lock() 65 hashDebug.m[out] = fmt.Sprintf("subkey %x %q", parent, desc) 66 hashDebug.Unlock() 67 } 68 return out, nil 69 } 70 71 // NewHash returns a new Hash. 72 // The caller is expected to Write data to it and then call Sum. 73 func NewHash(name string) (*Hash, error) { 74 h := &Hash{h: sha256.New(), name: name} 75 if debugHash { 76 fmt.Fprintf(os.Stderr, "HASH[%s]\n", h.name) 77 } 78 if n, err := h.Write(hashSalt); n != len(hashSalt) { 79 return nil, fmt.Errorf("wrote %d/%d bytes of hash salt with error %s", n, len(hashSalt), err) 80 } 81 if verify { 82 h.buf = new(bytes.Buffer) 83 } 84 return h, nil 85 } 86 87 // Write writes data to the running hash. 88 func (h *Hash) Write(b []byte) (int, error) { 89 if debugHash { 90 fmt.Fprintf(os.Stderr, "HASH[%s]: %q\n", h.name, b) 91 } 92 if h.buf != nil { 93 h.buf.Write(b) 94 } 95 return h.h.Write(b) 96 } 97 98 // Sum returns the hash of the data written previously. 99 func (h *Hash) Sum() [HashSize]byte { 100 var out [HashSize]byte 101 h.h.Sum(out[:0]) 102 if debugHash { 103 fmt.Fprintf(os.Stderr, "HASH[%s]: %x\n", h.name, out) 104 } 105 if h.buf != nil { 106 hashDebug.Lock() 107 if hashDebug.m == nil { 108 hashDebug.m = make(map[[HashSize]byte]string) 109 } 110 hashDebug.m[out] = h.buf.String() 111 hashDebug.Unlock() 112 } 113 return out 114 } 115 116 // In GODEBUG=gocacheverify=1 mode, 117 // hashDebug holds the input to every computed hash ID, 118 // so that we can work backward from the ID involved in a 119 // cache entry mismatch to a description of what should be there. 120 var hashDebug struct { 121 sync.Mutex 122 m map[[HashSize]byte]string 123 } 124 125 // reverseHash returns the input used to compute the hash id. 126 func reverseHash(id [HashSize]byte) string { 127 hashDebug.Lock() 128 s := hashDebug.m[id] 129 hashDebug.Unlock() 130 return s 131 } 132 133 var hashFileCache struct { 134 sync.Mutex 135 m map[string][HashSize]byte 136 } 137 138 // FileHash returns the hash of the named file. 139 // It caches repeated lookups for a given file, 140 // and the cache entry for a file can be initialized 141 // using SetFileHash. 142 // The hash used by FileHash is not the same as 143 // the hash used by NewHash. 144 func FileHash(file string) ([HashSize]byte, error) { 145 hashFileCache.Lock() 146 out, ok := hashFileCache.m[file] 147 hashFileCache.Unlock() 148 149 if ok { 150 return out, nil 151 } 152 153 h := sha256.New() 154 f, err := os.Open(file) 155 if err != nil { 156 if debugHash { 157 fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err) 158 } 159 return [HashSize]byte{}, err 160 } 161 _, err = io.Copy(h, f) 162 f.Close() 163 if err != nil { 164 if debugHash { 165 fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err) 166 } 167 return [HashSize]byte{}, err 168 } 169 h.Sum(out[:0]) 170 if debugHash { 171 fmt.Fprintf(os.Stderr, "HASH %s: %x\n", file, out) 172 } 173 174 SetFileHash(file, out) 175 return out, nil 176 } 177 178 // SetFileHash sets the hash returned by FileHash for file. 179 func SetFileHash(file string, sum [HashSize]byte) { 180 hashFileCache.Lock() 181 if hashFileCache.m == nil { 182 hashFileCache.m = make(map[string][HashSize]byte) 183 } 184 hashFileCache.m[file] = sum 185 hashFileCache.Unlock() 186 }