| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package buildid |
| |
| import ( |
| "bytes" |
| "crypto/sha256" |
| "fmt" |
| "io" |
| ) |
| |
| // FindAndHash reads all of r and returns the offsets of occurrences of id. |
| // While reading, findAndHash also computes and returns |
| // a hash of the content of r, but with occurrences of id replaced by zeros. |
| // FindAndHash reads bufSize bytes from r at a time. |
| // If bufSize == 0, FindAndHash uses a reasonable default. |
| func FindAndHash(r io.Reader, id string, bufSize int) (matches []int64, hash [32]byte, err error) { |
| if bufSize == 0 { |
| bufSize = 31 * 1024 // bufSize+little will likely fit in 32 kB |
| } |
| if len(id) > bufSize { |
| return nil, [32]byte{}, fmt.Errorf("buildid.FindAndHash: buffer too small") |
| } |
| zeros := make([]byte, len(id)) |
| idBytes := []byte(id) |
| |
| // The strategy is to read the file through buf, looking for id, |
| // but we need to worry about what happens if id is broken up |
| // and returned in parts by two different reads. |
| // We allocate a tiny buffer (at least len(id)) and a big buffer (bufSize bytes) |
| // next to each other in memory and then copy the tail of |
| // one read into the tiny buffer before reading new data into the big buffer. |
| // The search for id is over the entire tiny+big buffer. |
| tiny := (len(id) + 127) &^ 127 // round up to 128-aligned |
| buf := make([]byte, tiny+bufSize) |
| h := sha256.New() |
| start := tiny |
| for offset := int64(0); ; { |
| // The file offset maintained by the loop corresponds to &buf[tiny]. |
| // buf[start:tiny] is left over from previous iteration. |
| // After reading n bytes into buf[tiny:], we process buf[start:tiny+n]. |
| n, err := io.ReadFull(r, buf[tiny:]) |
| if err != io.ErrUnexpectedEOF && err != io.EOF && err != nil { |
| return nil, [32]byte{}, err |
| } |
| |
| // Process any matches. |
| for { |
| i := bytes.Index(buf[start:tiny+n], idBytes) |
| if i < 0 { |
| break |
| } |
| matches = append(matches, offset+int64(start+i-tiny)) |
| h.Write(buf[start : start+i]) |
| h.Write(zeros) |
| start += i + len(id) |
| } |
| if n < bufSize { |
| // Did not fill buffer, must be at end of file. |
| h.Write(buf[start : tiny+n]) |
| break |
| } |
| |
| // Process all but final tiny bytes of buf (bufSize = len(buf)-tiny). |
| // Note that start > len(buf)-tiny is possible, if the search above |
| // found an id ending in the final tiny fringe. That's OK. |
| if start < len(buf)-tiny { |
| h.Write(buf[start : len(buf)-tiny]) |
| start = len(buf) - tiny |
| } |
| |
| // Slide ending tiny-sized fringe to beginning of buffer. |
| copy(buf[0:], buf[bufSize:]) |
| start -= bufSize |
| offset += int64(bufSize) |
| } |
| h.Sum(hash[:0]) |
| return matches, hash, nil |
| } |
| |
| func Rewrite(w io.WriterAt, pos []int64, id string) error { |
| b := []byte(id) |
| for _, p := range pos { |
| if _, err := w.WriteAt(b, p); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |