hash.go raw

   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  }
 164