mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-08 22:31:04 +00:00
lib/model: Use errors.Wrap for pull errors (#5563)
This commit is contained in:
parent
0f80318ef6
commit
6940d79f5b
@ -67,6 +67,8 @@ var (
|
||||
errDirNotEmpty = errors.New("directory is not empty; files within are probably ignored on connected devices only")
|
||||
errNotAvailable = errors.New("no connected device has the required version of this file")
|
||||
errModified = errors.New("file modified but not rescanned; will try again later")
|
||||
errIncompatibleSymlink = errors.New("incompatible symlink entry; rescan with newer Syncthing on source")
|
||||
contextRemovingOldItem = "removing item to be replaced"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -325,7 +327,7 @@ func (f *sendReceiveFolder) processNeeded(ignores *ignore.Matcher, folderFiles *
|
||||
changed++
|
||||
|
||||
case runtime.GOOS == "windows" && fs.WindowsInvalidFilename(file.Name):
|
||||
f.newPullError("pull", file.Name, fs.ErrInvalidFilename)
|
||||
f.newPullError(file.Name, fs.ErrInvalidFilename)
|
||||
|
||||
case file.IsDeleted():
|
||||
if file.IsDirectory() {
|
||||
@ -495,7 +497,7 @@ nextFile:
|
||||
continue nextFile
|
||||
}
|
||||
}
|
||||
f.newPullError("pull", fileName, errNotAvailable)
|
||||
f.newPullError(fileName, errNotAvailable)
|
||||
}
|
||||
|
||||
return changed, fileDeletions, dirDeletions, nil
|
||||
@ -511,7 +513,7 @@ func (f *sendReceiveFolder) processDeletions(ignores *ignore.Matcher, fileDeleti
|
||||
|
||||
l.Debugln(f, "Deleting file", file.Name)
|
||||
if update, err := f.deleteFile(file, scanChan); err != nil {
|
||||
f.newPullError("delete file", file.Name, err)
|
||||
f.newPullError(file.Name, errors.Wrap(err, "delete file"))
|
||||
} else {
|
||||
dbUpdateChan <- update
|
||||
}
|
||||
@ -565,13 +567,17 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
|
||||
|
||||
info, err := f.fs.Lstat(file.Name)
|
||||
switch {
|
||||
// !!!
|
||||
// This is wrong: It deletes the file on disk regardless of
|
||||
// what it is (e.g. got updated -> conflict)
|
||||
// !!!
|
||||
// There is already something under that name, but it's a file/link.
|
||||
// Most likely a file/link is getting replaced with a directory.
|
||||
// Remove the file/link and fall through to directory creation.
|
||||
case err == nil && (!info.IsDir() || info.IsSymlink()):
|
||||
err = osutil.InWritableDir(f.fs.Remove, f.fs, file.Name)
|
||||
if err != nil {
|
||||
f.newPullError("dir replace", file.Name, err)
|
||||
f.newPullError(file.Name, errors.Wrap(err, "dir replace"))
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
@ -606,26 +612,26 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
|
||||
if err = osutil.InWritableDir(mkdir, f.fs, file.Name); err == nil {
|
||||
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
|
||||
} else {
|
||||
f.newPullError("dir mkdir", file.Name, err)
|
||||
f.newPullError(file.Name, errors.Wrap(err, "creating directory"))
|
||||
}
|
||||
return
|
||||
// Weird error when stat()'ing the dir. Probably won't work to do
|
||||
// anything else with it if we can't even stat() it.
|
||||
case err != nil:
|
||||
f.newPullError("dir stat", file.Name, err)
|
||||
f.newPullError(file.Name, errors.Wrap(err, "checking file to be replaced"))
|
||||
return
|
||||
}
|
||||
|
||||
// The directory already exists, so we just correct the mode bits. (We
|
||||
// don't handle modification times on directories, because that sucks...)
|
||||
// It's OK to change mode bits on stuff within non-writable directories.
|
||||
if f.IgnorePerms || file.NoPermissions {
|
||||
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
|
||||
} else if err := f.fs.Chmod(file.Name, mode|(fs.FileMode(info.Mode())&retainBits)); err == nil {
|
||||
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
|
||||
} else {
|
||||
f.newPullError("dir chmod", file.Name, err)
|
||||
if !f.IgnorePerms && !file.NoPermissions {
|
||||
if err := f.fs.Chmod(file.Name, mode|(fs.FileMode(info.Mode())&retainBits)); err != nil {
|
||||
f.newPullError(file.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
|
||||
}
|
||||
|
||||
// checkParent verifies that the thing we are handling lives inside a directory,
|
||||
@ -634,7 +640,7 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo
|
||||
parent := filepath.Dir(file)
|
||||
|
||||
if err := osutil.TraversesSymlink(f.fs, parent); err != nil {
|
||||
f.newPullError("traverses q", file, err)
|
||||
f.newPullError(file, errors.Wrap(err, "checking parent dirs"))
|
||||
return false
|
||||
}
|
||||
|
||||
@ -655,7 +661,7 @@ func (f *sendReceiveFolder) checkParent(file string, scanChan chan<- string) boo
|
||||
}
|
||||
l.Debugf("%v resurrecting parent directory of %v", f, file)
|
||||
if err := f.fs.MkdirAll(parent, 0755); err != nil {
|
||||
f.newPullError("resurrecting parent dir", file, err)
|
||||
f.newPullError(file, errors.Wrap(err, "resurrecting parent dir"))
|
||||
return false
|
||||
}
|
||||
scanChan <- parent
|
||||
@ -693,18 +699,21 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
|
||||
if len(file.SymlinkTarget) == 0 {
|
||||
// Index entry from a Syncthing predating the support for including
|
||||
// the link target in the index entry. We log this as an error.
|
||||
err = errors.New("incompatible symlink entry; rescan with newer Syncthing on source")
|
||||
f.newPullError("symlink", file.Name, err)
|
||||
f.newPullError(file.Name, errIncompatibleSymlink)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = f.fs.Lstat(file.Name); err == nil {
|
||||
// !!!
|
||||
// This is wrong: It deletes the file on disk regardless of
|
||||
// what it is (e.g. got updated -> conflict)
|
||||
// !!!
|
||||
// There is already something under that name. Remove it to replace
|
||||
// with the symlink. This also handles the "change symlink type"
|
||||
// path.
|
||||
err = osutil.InWritableDir(f.fs.Remove, f.fs, file.Name)
|
||||
if err != nil {
|
||||
f.newPullError("symlink remove", file.Name, err)
|
||||
f.newPullError(file.Name, errors.Wrap(err, "symlink remove"))
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -721,7 +730,7 @@ func (f *sendReceiveFolder) handleSymlink(file protocol.FileInfo, dbUpdateChan c
|
||||
if err = osutil.InWritableDir(createLink, f.fs, file.Name); err == nil {
|
||||
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleSymlink}
|
||||
} else {
|
||||
f.newPullError("symlink create", file.Name, err)
|
||||
f.newPullError(file.Name, errors.Wrap(err, "symlink create"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -749,7 +758,7 @@ func (f *sendReceiveFolder) handleDeleteDir(file protocol.FileInfo, ignores *ign
|
||||
}()
|
||||
|
||||
if err = f.deleteDir(file.Name, ignores, scanChan); err != nil {
|
||||
f.newPullError("delete dir", file.Name, err)
|
||||
f.newPullError(file.Name, errors.Wrap(err, "delete dir"))
|
||||
return
|
||||
}
|
||||
|
||||
@ -1025,7 +1034,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
|
||||
}
|
||||
|
||||
if err := f.CheckAvailableSpace(blocksSize); err != nil {
|
||||
f.newPullError("pulling file", file.Name, err)
|
||||
f.newPullError(file.Name, err)
|
||||
f.queue.Done(file.Name)
|
||||
return
|
||||
}
|
||||
@ -1137,7 +1146,7 @@ func (f *sendReceiveFolder) shortcutFile(file, curFile protocol.FileInfo, dbUpda
|
||||
|
||||
if !f.IgnorePerms && !file.NoPermissions {
|
||||
if err = f.fs.Chmod(file.Name, fs.FileMode(file.Permissions&0777)); err != nil {
|
||||
f.newPullError("shortcut", file.Name, err)
|
||||
f.newPullError(file.Name, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -1215,7 +1224,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
|
||||
for _, block := range state.blocks {
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
state.fail("folder stopped", f.ctx.Err())
|
||||
state.fail(errors.Wrap(f.ctx.Err(), "folder stopped"))
|
||||
break blocks
|
||||
default:
|
||||
}
|
||||
@ -1242,7 +1251,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
|
||||
|
||||
_, err = dstFd.WriteAt(buf, block.Offset)
|
||||
if err != nil {
|
||||
state.fail("dst write", err)
|
||||
state.fail(errors.Wrap(err, "dst write"))
|
||||
|
||||
}
|
||||
if offset == block.Offset {
|
||||
@ -1278,7 +1287,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
|
||||
|
||||
_, err = dstFd.WriteAt(buf, block.Offset)
|
||||
if err != nil {
|
||||
state.fail("dst write", err)
|
||||
state.fail(errors.Wrap(err, "dst write"))
|
||||
}
|
||||
if path == state.file.Name {
|
||||
state.copiedFromOrigin()
|
||||
@ -1383,7 +1392,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
|
||||
for {
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
state.fail("folder stopped", f.ctx.Err())
|
||||
state.fail(errors.Wrap(f.ctx.Err(), "folder stopped"))
|
||||
return
|
||||
default:
|
||||
}
|
||||
@ -1394,9 +1403,9 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
|
||||
selected, found := activity.leastBusy(candidates)
|
||||
if !found {
|
||||
if lastError != nil {
|
||||
state.fail("pull", lastError)
|
||||
state.fail(errors.Wrap(lastError, "pull"))
|
||||
} else {
|
||||
state.fail("pull", errNoDevice)
|
||||
state.fail(errors.Wrap(errNoDevice, "pull"))
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -1425,7 +1434,7 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
|
||||
// Save the block data we got from the cluster
|
||||
_, err = fd.WriteAt(buf, state.block.Offset)
|
||||
if err != nil {
|
||||
state.fail("save", err)
|
||||
state.fail(errors.Wrap(err, "save"))
|
||||
} else {
|
||||
state.pullDone(state.block)
|
||||
}
|
||||
@ -1501,7 +1510,7 @@ func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, file, curFile
|
||||
// non-empty directories).
|
||||
|
||||
if err = f.deleteDir(file.Name, ignores, scanChan); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, contextRemovingOldItem)
|
||||
}
|
||||
|
||||
case f.inConflict(curFile.Version, file.Version):
|
||||
@ -1555,7 +1564,7 @@ func (f *sendReceiveFolder) finisherRoutine(ignores *ignore.Matcher, in <-chan *
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
f.newPullError("finisher", state.file.Name, err)
|
||||
f.newPullError(state.file.Name, err)
|
||||
} else {
|
||||
blockStatsMut.Lock()
|
||||
blockStats["total"] += state.reused + state.copyTotal + state.pullTotal
|
||||
@ -1740,14 +1749,14 @@ func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan cha
|
||||
if isConflict(name) {
|
||||
l.Infoln("Conflict for", name, "which is already a conflict copy; not copying again.")
|
||||
if err := f.fs.Remove(name); err != nil && !fs.IsNotExist(err) {
|
||||
return err
|
||||
return errors.Wrap(err, contextRemovingOldItem)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if f.MaxConflicts == 0 {
|
||||
if err := f.fs.Remove(name); err != nil && !fs.IsNotExist(err) {
|
||||
return err
|
||||
return errors.Wrap(err, contextRemovingOldItem)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1778,7 +1787,7 @@ func (f *sendReceiveFolder) moveForConflict(name, lastModBy string, scanChan cha
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *sendReceiveFolder) newPullError(context, path string, err error) {
|
||||
func (f *sendReceiveFolder) newPullError(path string, err error) {
|
||||
f.pullErrorsMut.Lock()
|
||||
defer f.pullErrorsMut.Unlock()
|
||||
|
||||
@ -1788,8 +1797,13 @@ func (f *sendReceiveFolder) newPullError(context, path string, err error) {
|
||||
if _, ok := f.pullErrors[path]; ok {
|
||||
return
|
||||
}
|
||||
l.Infof("Puller (folder %s, file %q): %s: %v", f.Description(), path, context, err)
|
||||
f.pullErrors[path] = fmt.Sprintf("%s: %s", context, err.Error())
|
||||
|
||||
l.Infof("Puller (folder %s, file %q): %v", f.Description(), path, err)
|
||||
|
||||
// Establish context to differentiate from errors while scanning.
|
||||
// Use "syncing" as opposed to "pulling" as the latter might be used
|
||||
// for errors occurring specificly in the puller routine.
|
||||
f.pullErrors[path] = fmt.Sprintln("syncing:", err)
|
||||
}
|
||||
|
||||
func (f *sendReceiveFolder) clearPullErrors() {
|
||||
|
@ -488,7 +488,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
|
||||
toPull := <-pullChan
|
||||
|
||||
// Close the file, causing errors on further access
|
||||
toPull.sharedPullerState.fail("test", os.ErrNotExist)
|
||||
toPull.sharedPullerState.fail(os.ErrNotExist)
|
||||
|
||||
// Unblock copier
|
||||
go func() {
|
||||
|
@ -7,11 +7,12 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
@ -96,7 +97,7 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
// here.
|
||||
dir := filepath.Dir(s.tempName)
|
||||
if info, err := s.fs.Stat(dir); err != nil {
|
||||
s.failLocked("dst stat dir", err)
|
||||
s.failLocked(errors.Wrap(err, "ensuring parent dir is writeable"))
|
||||
return nil, err
|
||||
} else if info.Mode()&0200 == 0 {
|
||||
err := s.fs.Chmod(dir, 0755)
|
||||
@ -139,13 +140,13 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
// what the umask dictates.
|
||||
|
||||
if err := s.fs.Chmod(s.tempName, mode); err != nil {
|
||||
s.failLocked("dst create chmod", err)
|
||||
s.failLocked(errors.Wrap(err, "setting perms on temp file"))
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fd, err := s.fs.OpenFile(s.tempName, flags, mode)
|
||||
if err != nil {
|
||||
s.failLocked("dst create", err)
|
||||
s.failLocked(errors.Wrap(err, "opening temp file"))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -176,7 +177,7 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
l.Debugln("failed to remove temporary file:", remErr)
|
||||
}
|
||||
|
||||
s.failLocked("dst truncate", err)
|
||||
s.failLocked(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -190,19 +191,19 @@ func (s *sharedPullerState) tempFile() (io.WriterAt, error) {
|
||||
|
||||
// fail sets the error on the puller state compose of error, and marks the
|
||||
// sharedPullerState as failed. Is a no-op when called on an already failed state.
|
||||
func (s *sharedPullerState) fail(context string, err error) {
|
||||
func (s *sharedPullerState) fail(err error) {
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
|
||||
s.failLocked(context, err)
|
||||
s.failLocked(err)
|
||||
}
|
||||
|
||||
func (s *sharedPullerState) failLocked(context string, err error) {
|
||||
func (s *sharedPullerState) failLocked(err error) {
|
||||
if s.err != nil || err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.err = fmt.Errorf("%s: %s", context, err.Error())
|
||||
s.err = err
|
||||
}
|
||||
|
||||
func (s *sharedPullerState) failed() error {
|
||||
|
@ -38,6 +38,6 @@ func TestReadOnlyDir(t *testing.T) {
|
||||
t.Fatal("Unexpected nil fd")
|
||||
}
|
||||
|
||||
s.fail("Test done", nil)
|
||||
s.fail(nil)
|
||||
s.finalClose()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user