diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 92702c4b3..d02791b51 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -813,9 +813,10 @@ func (f *sendReceiveFolder) deleteDir(file protocol.FileInfo, snap *db.Snapshot, cur, hasCur := snap.Get(protocol.LocalDeviceID, file.Name) - if err = f.checkToBeDeleted(file, cur, hasCur, dbUpdateDeleteDir, dbUpdateChan, scanChan); err != nil { - if fs.IsNotExist(err) { + if err = f.checkToBeDeleted(file, cur, hasCur, scanChan); err != nil { + if fs.IsNotExist(err) || fs.IsErrCaseConflict(err) { err = nil + dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteDir} } return } @@ -860,9 +861,10 @@ func (f *sendReceiveFolder) deleteFileWithCurrent(file, cur protocol.FileInfo, h }) }() - if err = f.checkToBeDeleted(file, cur, hasCur, dbUpdateDeleteFile, dbUpdateChan, scanChan); err != nil { - if fs.IsNotExist(err) { + if err = f.checkToBeDeleted(file, cur, hasCur, scanChan); err != nil { + if fs.IsNotExist(err) || fs.IsErrCaseConflict(err) { err = nil + dbUpdateChan <- dbUpdateJob{file, dbUpdateDeleteFile} } return } @@ -945,7 +947,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn l.Debugln(f, "taking rename shortcut", source.Name, "->", target.Name) // Check that source is compatible with what we have in the DB - if err = f.checkToBeDeleted(source, cur, true, dbUpdateDeleteFile, dbUpdateChan, scanChan); err != nil { + if err = f.checkToBeDeleted(source, cur, true, scanChan); err != nil { return err } // Check that the target corresponds to what we have in the DB @@ -1979,26 +1981,25 @@ func (f *sendReceiveFolder) scanIfItemChanged(name string, stat fs.FileInfo, ite // checkToBeDeleted makes sure the file on disk is compatible with what there is // in the DB before the caller proceeds with actually deleting it. -// I.e. non-nil error status means "Do not delete!". -func (f *sendReceiveFolder) checkToBeDeleted(file, cur protocol.FileInfo, hasCur bool, updateType dbUpdateType, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error { +// I.e. non-nil error status means "Do not delete!" or "is already deleted". +func (f *sendReceiveFolder) checkToBeDeleted(file, cur protocol.FileInfo, hasCur bool, scanChan chan<- string) error { if err := osutil.TraversesSymlink(f.fs, filepath.Dir(file.Name)); err != nil { l.Debugln(f, "not deleting item behind symlink on disk, but update db", file.Name) - dbUpdateChan <- dbUpdateJob{file, updateType} return fs.ErrNotExist } stat, err := f.fs.Lstat(file.Name) - if !fs.IsNotExist(err) && err != nil { + deleted := fs.IsNotExist(err) || fs.IsErrCaseConflict(err) + if !deleted && err != nil { return err } - if fs.IsNotExist(err) { + if deleted { if hasCur && !cur.Deleted && !cur.IsUnsupported() { scanChan <- file.Name return errModified } l.Debugln(f, "not deleting item we don't have, but update db", file.Name) - dbUpdateChan <- dbUpdateJob{file, updateType} - return fs.ErrNotExist + return err } return f.scanIfItemChanged(file.Name, stat, cur, hasCur, scanChan) diff --git a/lib/model/folder_sendrecv_test.go b/lib/model/folder_sendrecv_test.go index 60b12d201..06dc30fba 100644 --- a/lib/model/folder_sendrecv_test.go +++ b/lib/model/folder_sendrecv_test.go @@ -781,18 +781,8 @@ func TestDeleteIgnorePerms(t *testing.T) { fi, err := scanner.CreateFileInfo(stat, name, ffs) must(t, err) ffs.Chmod(name, 0600) - scanChan := make(chan string) - dbUpdateChan := make(chan dbUpdateJob) - finished := make(chan struct{}) - go func() { - err = f.checkToBeDeleted(fi, fi, true, dbUpdateDeleteFile, dbUpdateChan, scanChan) - close(finished) - }() - select { - case <-scanChan: - <-finished - case <-finished: - } + scanChan := make(chan string, 1) + err = f.checkToBeDeleted(fi, fi, true, scanChan) must(t, err) } @@ -1325,6 +1315,40 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) { } } +func TestPullDeleteCaseConflict(t *testing.T) { + m, f := setupSendReceiveFolder() + defer cleanupSRFolder(f, m) + + name := "foo" + fi := protocol.FileInfo{Name: "Foo"} + dbUpdateChan := make(chan dbUpdateJob, 1) + scanChan := make(chan string) + + if fd, err := f.fs.Create(name); err != nil { + t.Fatal(err) + } else { + if _, err := fd.Write([]byte("data")); err != nil { + t.Fatal(err) + } + fd.Close() + } + f.deleteFileWithCurrent(fi, protocol.FileInfo{}, false, dbUpdateChan, scanChan) + select { + case <-dbUpdateChan: + default: + t.Error("Missing db update for file") + } + + snap := f.fset.Snapshot() + defer snap.Release() + f.deleteDir(fi, snap, dbUpdateChan, scanChan) + select { + case <-dbUpdateChan: + default: + t.Error("Missing db update for dir") + } +} + func cleanupSharedPullerState(s *sharedPullerState) { s.mut.Lock() defer s.mut.Unlock()