|
|
@@ -7,6 +7,7 @@
|
|
|
package model
|
|
|
|
|
|
import (
|
|
|
+ "bytes"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
"math/rand"
|
|
|
@@ -24,6 +25,7 @@ import (
|
|
|
"github.com/syncthing/syncthing/lib/osutil"
|
|
|
"github.com/syncthing/syncthing/lib/protocol"
|
|
|
"github.com/syncthing/syncthing/lib/scanner"
|
|
|
+ "github.com/syncthing/syncthing/lib/sha256"
|
|
|
"github.com/syncthing/syncthing/lib/sync"
|
|
|
"github.com/syncthing/syncthing/lib/versioner"
|
|
|
"github.com/syncthing/syncthing/lib/weakhash"
|
|
|
@@ -560,7 +562,7 @@ nextFile:
|
|
|
// we can just do a rename instead.
|
|
|
key := string(fi.Blocks[0].Hash)
|
|
|
for i, candidate := range buckets[key] {
|
|
|
- if scanner.BlocksEqual(candidate.Blocks, fi.Blocks) {
|
|
|
+ if blocksEqual(candidate.Blocks, fi.Blocks) {
|
|
|
// Remove the candidate from the bucket
|
|
|
lidx := len(buckets[key]) - 1
|
|
|
buckets[key][i] = buckets[key][lidx]
|
|
|
@@ -615,6 +617,21 @@ nextFile:
|
|
|
return changed
|
|
|
}
|
|
|
|
|
|
+// blocksEqual returns whether two slices of blocks are exactly the same hash
|
|
|
+// and index pair wise.
|
|
|
+func blocksEqual(src, tgt []protocol.BlockInfo) bool {
|
|
|
+ if len(tgt) != len(src) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ for i, sblk := range src {
|
|
|
+ if !bytes.Equal(sblk.Hash, tgt[i].Hash) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
// handleDir creates or updates the given directory
|
|
|
func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
|
|
|
// Used in the defer closure below, updated by the function body. Take
|
|
|
@@ -976,7 +993,7 @@ func (f *sendReceiveFolder) renameFile(source, target protocol.FileInfo, dbUpdat
|
|
|
func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState, dbUpdateChan chan<- dbUpdateJob) {
|
|
|
curFile, hasCurFile := f.model.CurrentFolderFile(f.folderID, file.Name)
|
|
|
|
|
|
- have, need := scanner.BlockDiff(curFile.Blocks, file.Blocks)
|
|
|
+ have, need := blockDiff(curFile.Blocks, file.Blocks)
|
|
|
|
|
|
if hasCurFile && len(need) == 0 {
|
|
|
// We are supposed to copy the entire file, and then fetch nothing. We
|
|
|
@@ -1013,7 +1030,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
|
|
|
|
|
tempName := fs.TempName(file.Name)
|
|
|
|
|
|
- scanner.PopulateOffsets(file.Blocks)
|
|
|
+ populateOffsets(file.Blocks)
|
|
|
|
|
|
var blocks []protocol.BlockInfo
|
|
|
var blocksSize int64
|
|
|
@@ -1024,7 +1041,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
|
|
tempBlocks, err := scanner.HashFile(f.ctx, f.fs, tempName, protocol.BlockSize, nil, false)
|
|
|
if err == nil {
|
|
|
// Check for any reusable blocks in the temp file
|
|
|
- tempCopyBlocks, _ := scanner.BlockDiff(tempBlocks, file.Blocks)
|
|
|
+ tempCopyBlocks, _ := blockDiff(tempBlocks, file.Blocks)
|
|
|
|
|
|
// block.String() returns a string unique to the block
|
|
|
existingBlocks := make(map[string]struct{}, len(tempCopyBlocks))
|
|
|
@@ -1108,6 +1125,39 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
|
|
copyChan <- cs
|
|
|
}
|
|
|
|
|
|
+// blockDiff returns lists of common and missing (to transform src into tgt)
|
|
|
+// blocks. Both block lists must have been created with the same block size.
|
|
|
+func blockDiff(src, tgt []protocol.BlockInfo) (have, need []protocol.BlockInfo) {
|
|
|
+ if len(tgt) == 0 && len(src) != 0 {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(tgt) != 0 && len(src) == 0 {
|
|
|
+ // Copy the entire file
|
|
|
+ return nil, tgt
|
|
|
+ }
|
|
|
+
|
|
|
+ for i := range tgt {
|
|
|
+ if i >= len(src) || !bytes.Equal(tgt[i].Hash, src[i].Hash) {
|
|
|
+ // Copy differing block
|
|
|
+ need = append(need, tgt[i])
|
|
|
+ } else {
|
|
|
+ have = append(have, tgt[i])
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return have, need
|
|
|
+}
|
|
|
+
|
|
|
+// populateOffsets sets the Offset field on each block
|
|
|
+func populateOffsets(blocks []protocol.BlockInfo) {
|
|
|
+ var offset int64
|
|
|
+ for i := range blocks {
|
|
|
+ blocks[i].Offset = offset
|
|
|
+ offset += int64(blocks[i].Size)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// shortcutFile sets file mode and modification time, when that's the only
|
|
|
// thing that has changed.
|
|
|
func (f *sendReceiveFolder) shortcutFile(file protocol.FileInfo) error {
|
|
|
@@ -1207,7 +1257,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
|
|
|
buf = buf[:int(block.Size)]
|
|
|
|
|
|
found, err := weakHashFinder.Iterate(block.WeakHash, buf, func(offset int64) bool {
|
|
|
- if _, err := scanner.VerifyBuffer(buf, block); err != nil {
|
|
|
+ if _, err := verifyBuffer(buf, block); err != nil {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
@@ -1242,7 +1292,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
- hash, err := scanner.VerifyBuffer(buf, block)
|
|
|
+ hash, err := verifyBuffer(buf, block)
|
|
|
if err != nil {
|
|
|
if hash != nil {
|
|
|
l.Debugf("Finder block mismatch in %s:%s:%d expected %q got %q", folder, path, index, block.Hash, hash)
|
|
|
@@ -1292,6 +1342,24 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func verifyBuffer(buf []byte, block protocol.BlockInfo) ([]byte, error) {
|
|
|
+ if len(buf) != int(block.Size) {
|
|
|
+ return nil, fmt.Errorf("length mismatch %d != %d", len(buf), block.Size)
|
|
|
+ }
|
|
|
+ hf := sha256.New()
|
|
|
+ _, err := hf.Write(buf)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ hash := hf.Sum(nil)
|
|
|
+
|
|
|
+ if !bytes.Equal(hash, block.Hash) {
|
|
|
+ return hash, fmt.Errorf("hash mismatch %x != %x", hash, block.Hash)
|
|
|
+ }
|
|
|
+
|
|
|
+ return hash, nil
|
|
|
+}
|
|
|
+
|
|
|
func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPullerState) {
|
|
|
for state := range in {
|
|
|
if state.failed() != nil {
|
|
|
@@ -1346,7 +1414,7 @@ func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *
|
|
|
|
|
|
// Verify that the received block matches the desired hash, if not
|
|
|
// try pulling it from another device.
|
|
|
- _, lastError = scanner.VerifyBuffer(buf, state.block)
|
|
|
+ _, lastError = verifyBuffer(buf, state.block)
|
|
|
if lastError != nil {
|
|
|
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "hash mismatch")
|
|
|
continue
|