src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }