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  }