|
@@ -16,8 +16,6 @@
|
|
|
package model
|
|
|
|
|
|
import (
|
|
|
- "bytes"
|
|
|
- "crypto/sha256"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
"io/ioutil"
|
|
@@ -158,16 +156,10 @@ loop:
|
|
|
}
|
|
|
p.model.setState(p.folder, FolderSyncing)
|
|
|
tries := 0
|
|
|
- checksum := false
|
|
|
for {
|
|
|
tries++
|
|
|
- // Last resort mode, to get around corrupt/invalid block maps.
|
|
|
- if tries == 10 {
|
|
|
- l.Infoln("Desperation mode ON")
|
|
|
- checksum = true
|
|
|
- }
|
|
|
|
|
|
- changed := p.pullerIteration(checksum, curIgnores)
|
|
|
+ changed := p.pullerIteration(curIgnores)
|
|
|
if debug {
|
|
|
l.Debugln(p, "changed", changed)
|
|
|
}
|
|
@@ -255,7 +247,7 @@ func (p *Puller) String() string {
|
|
|
// returns the number items that should have been synced (even those that
|
|
|
// might have failed). One puller iteration handles all files currently
|
|
|
// flagged as needed in the folder.
|
|
|
-func (p *Puller) pullerIteration(checksum bool, ignores *ignore.Matcher) int {
|
|
|
+func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
|
|
|
pullChan := make(chan pullBlockState)
|
|
|
copyChan := make(chan copyBlocksState)
|
|
|
finisherChan := make(chan *sharedPullerState)
|
|
@@ -272,7 +264,7 @@ func (p *Puller) pullerIteration(checksum bool, ignores *ignore.Matcher) int {
|
|
|
copyWg.Add(1)
|
|
|
go func() {
|
|
|
// copierRoutine finishes when copyChan is closed
|
|
|
- p.copierRoutine(copyChan, pullChan, finisherChan, checksum)
|
|
|
+ p.copierRoutine(copyChan, pullChan, finisherChan)
|
|
|
copyWg.Done()
|
|
|
}()
|
|
|
}
|
|
@@ -613,7 +605,7 @@ func (p *Puller) shortcutSymlink(curFile, file protocol.FileInfo) {
|
|
|
|
|
|
// copierRoutine reads copierStates until the in channel closes and performs
|
|
|
// the relevant copies when possible, or passes it to the puller routine.
|
|
|
-func (p *Puller) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pullBlockState, out chan<- *sharedPullerState, checksum bool) {
|
|
|
+func (p *Puller) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pullBlockState, out chan<- *sharedPullerState) {
|
|
|
buf := make([]byte, protocol.BlockSize)
|
|
|
|
|
|
nextFile:
|
|
@@ -649,7 +641,6 @@ nextFile:
|
|
|
}
|
|
|
p.model.fmut.RUnlock()
|
|
|
|
|
|
- hasher := sha256.New()
|
|
|
for _, block := range state.blocks {
|
|
|
buf = buf[:int(block.Size)]
|
|
|
found := p.model.finder.Iterate(block.Hash, func(folder, file string, index uint32) bool {
|
|
@@ -673,12 +664,9 @@ nextFile:
|
|
|
return false
|
|
|
}
|
|
|
|
|
|
- // Only done on second to last puller attempt
|
|
|
- if checksum {
|
|
|
- hasher.Write(buf)
|
|
|
- hash := hasher.Sum(nil)
|
|
|
- hasher.Reset()
|
|
|
- if !bytes.Equal(hash, block.Hash) {
|
|
|
+ hash, err := scanner.VerifyBuffer(buf, block)
|
|
|
+ if err != nil {
|
|
|
+ if hash != nil {
|
|
|
if debug {
|
|
|
l.Debugf("Finder block mismatch in %s:%s:%d expected %q got %q", folder, file, index, block.Hash, hash)
|
|
|
}
|
|
@@ -686,8 +674,10 @@ nextFile:
|
|
|
if err != nil {
|
|
|
l.Warnln("finder fix:", err)
|
|
|
}
|
|
|
- return false
|
|
|
+ } else if debug {
|
|
|
+ l.Debugln("Finder failed to verify buffer", err)
|
|
|
}
|
|
|
+ return false
|
|
|
}
|
|
|
|
|
|
_, err = dstFd.WriteAt(buf, block.Offset)
|
|
@@ -722,20 +712,9 @@ nextFile:
|
|
|
}
|
|
|
|
|
|
func (p *Puller) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPullerState) {
|
|
|
-nextBlock:
|
|
|
for state := range in {
|
|
|
if state.failed() != nil {
|
|
|
- continue nextBlock
|
|
|
- }
|
|
|
-
|
|
|
- // Select the least busy device to pull the block from. If we found no
|
|
|
- // feasible device at all, fail the block (and in the long run, the
|
|
|
- // file).
|
|
|
- potentialDevices := p.model.availability(p.folder, state.file.Name)
|
|
|
- selected := activity.leastBusy(potentialDevices)
|
|
|
- if selected == (protocol.DeviceID{}) {
|
|
|
- state.earlyClose("pull", errNoDevice)
|
|
|
- continue nextBlock
|
|
|
+ continue
|
|
|
}
|
|
|
|
|
|
// Get an fd to the temporary file. Tehcnically we don't need it until
|
|
@@ -743,45 +722,58 @@ nextBlock:
|
|
|
// no point in issuing the request to the network.
|
|
|
fd, err := state.tempFile()
|
|
|
if err != nil {
|
|
|
- continue nextBlock
|
|
|
+ continue
|
|
|
}
|
|
|
|
|
|
- // Fetch the block, while marking the selected device as in use so that
|
|
|
- // leastBusy can select another device when someone else asks.
|
|
|
- activity.using(selected)
|
|
|
- buf, err := p.model.requestGlobal(selected, p.folder, state.file.Name, state.block.Offset, int(state.block.Size), state.block.Hash)
|
|
|
- activity.done(selected)
|
|
|
- if err != nil {
|
|
|
- state.earlyClose("pull", err)
|
|
|
- continue nextBlock
|
|
|
- }
|
|
|
+ var lastError error
|
|
|
+ potentialDevices := p.model.availability(p.folder, state.file.Name)
|
|
|
+ for {
|
|
|
+ // Select the least busy device to pull the block from. If we found no
|
|
|
+ // feasible device at all, fail the block (and in the long run, the
|
|
|
+ // file).
|
|
|
+ selected := activity.leastBusy(potentialDevices)
|
|
|
+ if selected == (protocol.DeviceID{}) {
|
|
|
+ if lastError != nil {
|
|
|
+ state.earlyClose("pull", lastError)
|
|
|
+ } else {
|
|
|
+ state.earlyClose("pull", errNoDevice)
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
|
|
|
- // Save the block data we got from the cluster
|
|
|
- _, err = fd.WriteAt(buf, state.block.Offset)
|
|
|
- if err != nil {
|
|
|
- state.earlyClose("save", err)
|
|
|
- continue nextBlock
|
|
|
- }
|
|
|
+ potentialDevices = removeDevice(potentialDevices, selected)
|
|
|
|
|
|
- state.pullDone()
|
|
|
- out <- state.sharedPullerState
|
|
|
+ // Fetch the block, while marking the selected device as in use so that
|
|
|
+ // leastBusy can select another device when someone else asks.
|
|
|
+ activity.using(selected)
|
|
|
+ buf, lastError := p.model.requestGlobal(selected, p.folder, state.file.Name, state.block.Offset, int(state.block.Size), state.block.Hash)
|
|
|
+ activity.done(selected)
|
|
|
+ if lastError != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify that the received block matches the desired hash, if not
|
|
|
+ // try pulling it from another device.
|
|
|
+ _, lastError = scanner.VerifyBuffer(buf, state.block)
|
|
|
+ if lastError != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // Save the block data we got from the cluster
|
|
|
+ _, err = fd.WriteAt(buf, state.block.Offset)
|
|
|
+ if err != nil {
|
|
|
+ state.earlyClose("save", err)
|
|
|
+ } else {
|
|
|
+ state.pullDone()
|
|
|
+ out <- state.sharedPullerState
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func (p *Puller) performFinish(state *sharedPullerState) {
|
|
|
- // Verify the file against expected hashes
|
|
|
- fd, err := os.Open(state.tempName)
|
|
|
- if err != nil {
|
|
|
- l.Warnln("puller: final:", err)
|
|
|
- return
|
|
|
- }
|
|
|
- err = scanner.Verify(fd, protocol.BlockSize, state.file.Blocks)
|
|
|
- fd.Close()
|
|
|
- if err != nil {
|
|
|
- l.Infoln("puller:", state.file.Name, err, "(file changed during pull?)")
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
+ var err error
|
|
|
// Set the correct permission bits on the new file
|
|
|
if !p.ignorePerms {
|
|
|
err = os.Chmod(state.tempName, os.FileMode(state.file.Flags&0777))
|
|
@@ -893,3 +885,13 @@ func invalidateFolder(cfg *config.Configuration, folderID string, err error) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+func removeDevice(devices []protocol.DeviceID, device protocol.DeviceID) []protocol.DeviceID {
|
|
|
+ for i := range devices {
|
|
|
+ if devices[i] == device {
|
|
|
+ devices[i] = devices[len(devices)-1]
|
|
|
+ return devices[:len(devices)-1]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return devices
|
|
|
+}
|